2727import com .cloud .storage .Storage ;
2828import com .cloud .storage .StoragePool ;
2929import com .cloud .storage .Volume ;
30+ import com .cloud .storage .VolumeVO ;
3031import com .cloud .utils .Pair ;
3132import com .cloud .utils .exception .CloudRuntimeException ;
3233import org .apache .cloudstack .engine .subsystem .api .storage .ChapInfo ;
@@ -64,13 +65,14 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
6465
6566 @ Inject private StoragePoolDetailsDao storagePoolDetailsDao ;
6667 @ Inject private PrimaryDataStoreDao storagePoolDao ;
68+ @ Inject private com .cloud .storage .dao .VolumeDao volumeDao ;
6769 @ Override
6870 public Map <String , String > getCapabilities () {
6971 s_logger .trace ("OntapPrimaryDatastoreDriver: getCapabilities: Called" );
7072 Map <String , String > mapCapabilities = new HashMap <>();
71-
72- mapCapabilities .put (DataStoreCapabilities .STORAGE_SYSTEM_SNAPSHOT .toString (), Boolean .TRUE .toString ());
73- mapCapabilities .put (DataStoreCapabilities .CAN_CREATE_VOLUME_FROM_SNAPSHOT .toString (), Boolean .TRUE .toString ());
73+ // RAW managed initial implementation: snapshot features not yet supported
74+ mapCapabilities .put (DataStoreCapabilities .STORAGE_SYSTEM_SNAPSHOT .toString (), Boolean .FALSE .toString ());
75+ mapCapabilities .put (DataStoreCapabilities .CAN_CREATE_VOLUME_FROM_SNAPSHOT .toString (), Boolean .FALSE .toString ());
7476
7577 return mapCapabilities ;
7678 }
@@ -116,36 +118,176 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
116118 createCmdResult = new CreateCmdResult (null , new Answer (null , false , errMsg ));
117119 createCmdResult .setResult (e .toString ());
118120 } finally {
119- s_logger .info ("Volume creation successfully completed" );
121+ if (createCmdResult != null && createCmdResult .isSuccess ()) {
122+ s_logger .info ("createAsync: Volume metadata created successfully. Path: {}" , path );
123+ }
120124 callback .complete (createCmdResult );
121125 }
122126 }
123127
128+ /**
129+ * Creates CloudStack volume based on storage protocol type (NFS or iSCSI).
130+ *
131+ * For Managed NFS (Option 2 Implementation):
132+ * - Returns only UUID without creating qcow2 file
133+ * - KVM hypervisor creates qcow2 file automatically during VM deployment
134+ * - ONTAP volume provides the backing NFS storage
135+ *
136+ * For iSCSI/Block Storage:
137+ * - Creates LUN via ONTAP REST API
138+ * - Returns LUN path for direct attachment
139+ */
124140 private String createCloudStackVolumeForTypeVolume (DataStore dataStore , DataObject dataObject ) {
125141 StoragePoolVO storagePool = storagePoolDao .findById (dataStore .getId ());
126142 if (storagePool == null ) {
127- s_logger .error ("createCloudStackVolume : Storage Pool not found for id: " + dataStore .getId ());
128- throw new CloudRuntimeException ("createCloudStackVolume : Storage Pool not found for id: " + dataStore .getId ());
143+ s_logger .error ("createCloudStackVolumeForTypeVolume : Storage Pool not found for id: {}" , dataStore .getId ());
144+ throw new CloudRuntimeException ("createCloudStackVolumeForTypeVolume : Storage Pool not found for id: " + dataStore .getId ());
129145 }
146+
130147 Map <String , String > details = storagePoolDetailsDao .listDetailsKeyPairs (dataStore .getId ());
148+ String protocol = details .get (Constants .PROTOCOL );
149+
150+ if (ProtocolType .NFS .name ().equalsIgnoreCase (protocol )) {
151+ return createManagedNfsVolume (dataStore , dataObject , storagePool );
152+ } else if (ProtocolType .ISCSI .name ().equalsIgnoreCase (protocol )) {
153+ return createManagedBlockVolume (dataStore , dataObject , storagePool , details );
154+ } else {
155+ String errMsg = String .format ("createCloudStackVolumeForTypeVolume: Unsupported protocol [%s]" , protocol );
156+ s_logger .error (errMsg );
157+ throw new CloudRuntimeException (errMsg );
158+ }
159+ }
160+
161+ /**
162+ * Creates Managed NFS Volume with ONTAP backing storage.
163+ *
164+ * Architecture: 1 CloudStack Storage Pool = 1 ONTAP Volume (shared by all volumes)
165+ *
166+ * Flow:
167+ * 1. createAsync() stores volume metadata and NFS mount point
168+ * 2. Volume attach triggers ManagedNfsStorageAdaptor.connectPhysicalDisk()
169+ * 3. KVM mounts: nfs://nfsServer/junctionPath to /mnt/volumeUuid
170+ * 4. Libvirt creates qcow2 file via storageVolCreateXML()
171+ * 5. File created at: /vol/ontap_volume/volumeUuid (on ONTAP)
172+ *
173+ * Key Details:
174+ * - All volumes in same pool share the same ONTAP volume NFS export
175+ * - Each volume gets separate libvirt mount point: /mnt/<volumeUuid>
176+ * - All qcow2 files stored in same ONTAP volume: /vol/<pool_volume_name>/
177+ * - volume._iScsiName stores the NFS junction path (pool.path)
178+ *
179+ * @param dataStore CloudStack data store (storage pool)
180+ * @param dataObject Volume data object
181+ * @param storagePool Storage pool VO
182+ * @return Volume UUID (used as filename for qcow2 file)
183+ */
184+ private String createManagedNfsVolume (DataStore dataStore , DataObject dataObject , StoragePoolVO storagePool ) {
185+ VolumeInfo volumeInfo = (VolumeInfo ) dataObject ;
186+ VolumeVO volume = volumeDao .findById (volumeInfo .getId ());
187+ String volumeUuid = volumeInfo .getUuid ();
188+
189+ // Get the NFS junction path from storage pool
190+ // This is the path that was set during pool creation (e.g., "/my_pool_volume")
191+ String junctionPath = storagePool .getPath ();
192+
193+ // Update volume metadata in CloudStack database
194+ volume .setPoolType (Storage .StoragePoolType .ManagedNFS );
195+ volume .setPoolId (dataStore .getId ());
196+ volume .setPath (volumeUuid ); // Filename for qcow2 file
197+
198+ // CRITICAL: Store junction path in _iScsiName field
199+ // CloudStack will use this in AttachCommand as DiskTO.MOUNT_POINT
200+ // ManagedNfsStorageAdaptor will mount: nfs://hostAddress/junctionPath to /mnt/volumeUuid
201+ volume .set_iScsiName (junctionPath );
202+
203+ volumeDao .update (volume .getId (), volume );
204+
205+ s_logger .info ("ONTAP Managed NFS Volume Created: uuid={}, path={}, junctionPath={}, format=QCOW2, " +
206+ "pool={}, size={}GB. Libvirt will create qcow2 file at mount time." ,
207+ volumeUuid , volumeUuid , junctionPath , storagePool .getName (),
208+ volumeInfo .getSize () / (1024 * 1024 * 1024 ));
209+
210+ // Optional: Prepare ONTAP volume for optimal qcow2 storage (future enhancement)
211+ // prepareOntapVolumeForQcow2Storage(dataStore, volumeInfo);
212+
213+ return volumeUuid ;
214+ }
215+
216+ /**
217+ * Creates iSCSI/Block volume by calling ONTAP REST API to create a LUN.
218+ *
219+ * For block storage (iSCSI), the storage provider must create the LUN
220+ * before CloudStack can use it. This is different from NFS where the
221+ * hypervisor creates the file.
222+ *
223+ * @param dataStore CloudStack data store
224+ * @param dataObject Volume data object
225+ * @param storagePool Storage pool VO
226+ * @param details Storage pool details containing ONTAP connection info
227+ * @return LUN path/name for iSCSI attachment
228+ */
229+ private String createManagedBlockVolume (DataStore dataStore , DataObject dataObject ,
230+ StoragePoolVO storagePool , Map <String , String > details ) {
131231 StorageStrategy storageStrategy = getStrategyByStoragePoolDetails (details );
132- s_logger .info ("createCloudStackVolumeForTypeVolume: Connection to Ontap SVM [{}] successful, preparing CloudStackVolumeRequest" , details .get (Constants .SVM_NAME ));
232+
233+ s_logger .info ("createManagedBlockVolume: Creating iSCSI LUN on ONTAP SVM [{}]" , details .get (Constants .SVM_NAME ));
234+
133235 CloudStackVolume cloudStackVolumeRequest = Utility .createCloudStackVolumeRequestByProtocol (storagePool , details , (VolumeInfo ) dataObject );
236+
134237 CloudStackVolume cloudStackVolume = storageStrategy .createCloudStackVolume (cloudStackVolumeRequest );
135- if (ProtocolType .ISCSI .name ().equalsIgnoreCase (details .get (Constants .PROTOCOL )) && cloudStackVolume .getLun () != null && cloudStackVolume .getLun ().getName () != null ) {
136- return cloudStackVolume .getLun ().getName ();
137- } else if (ProtocolType .NFS .name ().equalsIgnoreCase (details .get (Constants .PROTOCOL ))) {
138- return cloudStackVolume .getFile ().getName ();
238+
239+ if (cloudStackVolume .getLun () != null && cloudStackVolume .getLun ().getName () != null ) {
240+ String lunPath = cloudStackVolume .getLun ().getName ();
241+ s_logger .info ("createManagedBlockVolume: iSCSI LUN created successfully: {}" , lunPath );
242+ return lunPath ;
139243 } else {
140- String errMsg = "createCloudStackVolumeForTypeVolume: Volume creation failed. Lun or Lun Path is null for dataObject: " + dataObject ;
244+ String errMsg = String .format ("createManagedBlockVolume: LUN creation failed for volume [%s]. " +
245+ "LUN or LUN path is null." , dataObject .getUuid ());
141246 s_logger .error (errMsg );
142247 throw new CloudRuntimeException (errMsg );
143248 }
144249 }
145250
251+ /**
252+ * Optional: Prepares ONTAP volume for optimal qcow2 file storage.
253+ *
254+ * Future enhancements can include:
255+ * - Enable compression for qcow2 files
256+ * - Set QoS policies
257+ * - Enable deduplication
258+ * - Configure snapshot policies
259+ *
260+ * This is a placeholder for ONTAP-specific optimizations.
261+ */
262+ private void prepareOntapVolumeForQcow2Storage (DataStore dataStore , VolumeInfo volumeInfo ) {
263+ // TODO: Implement ONTAP volume optimizations
264+ // Examples:
265+ // - storageStrategy.enableCompression(volumePath)
266+ // - storageStrategy.setQosPolicy(volumePath, iops)
267+ // - storageStrategy.enableDeduplication(volumePath)
268+ s_logger .debug ("prepareOntapVolumeForQcow2Storage: Placeholder for future ONTAP optimizations" );
269+ }
270+
146271 @ Override
147272 public void deleteAsync (DataStore store , DataObject data , AsyncCompletionCallback <CommandResult > callback ) {
148-
273+ CommandResult commandResult = new CommandResult ();
274+ try {
275+ if (store == null || data == null ) {
276+ throw new CloudRuntimeException ("deleteAsync: store or data is null" );
277+ }
278+ if (data .getType () == DataObjectType .VOLUME ) {
279+ StoragePoolVO storagePool = storagePoolDao .findById (store .getId ());
280+ Map <String , String > details = storagePoolDetailsDao .listDetailsKeyPairs (store .getId ());
281+ if (ProtocolType .NFS .name ().equalsIgnoreCase (details .get (Constants .PROTOCOL ))) {
282+ // ManagedNFS qcow2 backing file deletion handled by KVM host/libvirt; nothing to do via ONTAP REST.
283+ s_logger .info ("deleteAsync: ManagedNFS volume {} no-op ONTAP deletion" , data .getId ());
284+ }
285+ }
286+ } catch (Exception e ) {
287+ commandResult .setResult (e .getMessage ());
288+ } finally {
289+ callback .complete (commandResult );
290+ }
149291 }
150292
151293 @ Override
@@ -219,7 +361,7 @@ public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, Qual
219361
220362 @ Override
221363 public boolean canProvideStorageStats () {
222- return true ;
364+ return false ;
223365 }
224366
225367 @ Override
@@ -229,7 +371,7 @@ public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
229371
230372 @ Override
231373 public boolean canProvideVolumeStats () {
232- return true ;
374+ return false ; // Not yet implemented for RAW managed NFS
233375 }
234376
235377 @ Override
@@ -291,4 +433,4 @@ private StorageStrategy getStrategyByStoragePoolDetails(Map<String, String> deta
291433 throw new CloudRuntimeException ("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details .get (Constants .SVM_NAME ) + "] failed" );
292434 }
293435 }
294- }
436+ }
0 commit comments