Skip to content

Commit 5895ae3

Browse files
Locharla, SandeepLocharla, Sandeep
authored andcommitted
CSTACKEX-46: Fixed code as per comments received
1 parent dfc1ee9 commit 5895ae3

File tree

9 files changed

+434
-161
lines changed

9 files changed

+434
-161
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
# Apache CloudStack - NetApp ONTAP Storage Plugin
21+
22+
## Overview
23+
24+
The NetApp ONTAP Storage Plugin provides integration between Apache CloudStack and NetApp ONTAP storage systems. This plugin enables CloudStack to provision and manage primary storage on ONTAP clusters, supporting both NAS (NFS) and SAN (iSCSI) protocols.
25+
26+
## Features
27+
28+
- **Primary Storage Support**: Provision and manage primary storage pools on NetApp ONTAP
29+
- **Multiple Protocols**: Support for NFS 3.0 and iSCSI protocols
30+
- **Unified Storage**: Integration with traditional ONTAP unified storage architecture
31+
- **KVM Hypervisor Support**: Supports KVM hypervisor environments
32+
- **Managed Storage**: Operates as managed storage with full lifecycle management
33+
- **Flexible Scoping**: Support for Zone-wide and Cluster-scoped storage pools
34+
35+
## Architecture
36+
37+
### Component Structure
38+
39+
| Package | Description |
40+
|---------|-------------------------------------------------------|
41+
| `driver` | Primary datastore driver implementation |
42+
| `feign` | REST API clients and data models for ONTAP operations |
43+
| `lifecycle` | Storage pool lifecycle management |
44+
| `listener` | Host connection event handlers |
45+
| `provider` | Main provider and strategy factory |
46+
| `service` | ONTAP Storage strategy implementations (NAS/SAN) |
47+
| `utils` | Constants and helper utilities |
48+
49+
## Requirements
50+
51+
### ONTAP Requirements
52+
53+
- NetApp ONTAP 9.15.1 or higher
54+
- Storage Virtual Machine (SVM) configured with appropriate protocols enabled
55+
- Management LIF accessible from CloudStack management server
56+
- Data LIF(s) accessible from hypervisor hosts and are of IPv4 type
57+
- Aggregates assigned to the SVM with sufficient capacity
58+
59+
### CloudStack Requirements
60+
61+
- Apache CloudStack current version or higher
62+
- KVM hypervisor hosts
63+
- For iSCSI: Hosts must have iSCSI initiator configured with valid IQN
64+
- For NFS: Hosts must have NFS client packages installed
65+
66+
### Minimum Volume Size
67+
68+
ONTAP requires a minimum volume size of **1.56 GB** (1,677,721,600 bytes). The plugin will automatically adjust requested sizes below this threshold.
69+
70+
## Configuration
71+
72+
### Storage Pool Creation Parameters
73+
74+
When creating an ONTAP primary storage pool, provide the following details in the URL field (semicolon-separated key=value pairs):
75+
76+
| Parameter | Required | Description |
77+
|-----------|----------|-------------|
78+
| `username` | Yes | ONTAP cluster admin username |
79+
| `password` | Yes | ONTAP cluster admin password |
80+
| `svmName` | Yes | Storage Virtual Machine name |
81+
| `protocol` | Yes | Storage protocol (`NFS3` or `ISCSI`) |
82+
| `managementLIF` | Yes | ONTAP cluster management LIF IP address |
83+
84+
### Example URL Format
85+
86+
```
87+
username=admin;password=secretpass;svmName=svm1;protocol=ISCSI;managementLIF=192.168.1.100
88+
```
89+
90+
## Port Configuration
91+
92+
| Protocol | Default Port |
93+
|----------|--------------|
94+
| NFS | 2049 |
95+
| iSCSI | 3260 |
96+
| ONTAP Management API | 443 (HTTPS) |
97+
98+
## Limitations
99+
100+
- Supports only **KVM** hypervisor
101+
- Supports only **Unified ONTAP** storage (disaggregated not supported)
102+
- Supports only **NFS3** and **iSCSI** protocols
103+
- IPv6 type and FQDN LIFs are not supported
104+
105+
## Troubleshooting
106+
107+
### Common Issues
108+
109+
1. **Connection Failures**
110+
- Verify management LIF is reachable from CloudStack management server
111+
- Check firewall rules for port 443
112+
113+
2. **Protocol Errors**
114+
- Ensure the protocol (NFS/iSCSI) is enabled on the SVM
115+
- Verify Data LIFs are configured for the protocol
116+
117+
3. **Capacity Errors**
118+
- Check aggregate space availability
119+
- Ensure requested volume size meets minimum requirements (1.56 GB)
120+
121+
4. **Host Connection Issues**
122+
- For iSCSI: Verify host IQN is properly configured in host's storage URL
123+
- For NFS: Ensure NFS client is installed and running

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java

Lines changed: 19 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,7 @@
6969

7070
/**
7171
* Primary datastore driver for NetApp ONTAP storage systems.
72-
* This driver handles volume lifecycle operations (create, delete, grant/revoke access)
73-
* for both iSCSI (LUN-based) and NFS protocols against ONTAP storage backends.
74-
*
75-
* For iSCSI protocol:
76-
* - Creates LUNs on ONTAP and maps them to initiator groups (igroups)
77-
* - Manages LUN mappings for host access control
78-
*
79-
* For NFS protocol:
80-
* - Delegates file operations to KVM host/libvirt
81-
* - ONTAP volume/export management handled at storage pool creation time
72+
* Handles volume lifecycle operations for iSCSI and NFS protocols.
8273
*/
8374
public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
8475

@@ -111,26 +102,16 @@ public DataStoreTO getStoreTO(DataStore store) {
111102
}
112103

113104
/**
114-
* Asynchronously creates a volume on the ONTAP storage system.
115-
*
116-
* For iSCSI protocol:
117-
* - Creates a LUN on ONTAP via the SAN strategy
118-
* - Stores LUN UUID and name in volume_details table for later reference
119-
* - Creates a LUN mapping to the appropriate igroup (based on cluster/zone scope)
120-
* - Sets the iSCSI path on the volume for host attachment
121-
*
122-
* For NFS protocol:
123-
* - Associates the volume with the storage pool (actual file creation handled by hypervisor)
124-
*
125-
* @param dataStore The target data store (storage pool)
126-
* @param dataObject The volume to create
127-
* @param callback Callback to notify completion
105+
* Creates a volume on the ONTAP storage system.
128106
*/
129107
@Override
130108
public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
131109
CreateCmdResult createCmdResult = null;
132110
String errMsg;
133111

112+
if (dataObject == null) {
113+
throw new InvalidParameterValueException("createAsync: dataObject should not be null");
114+
}
134115
if (dataStore == null) {
135116
throw new InvalidParameterValueException("createAsync: dataStore should not be null");
136117
}
@@ -150,6 +131,7 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
150131
s_logger.error("createAsync: Storage Pool not found for id: " + dataStore.getId());
151132
throw new CloudRuntimeException("createAsync: Storage Pool not found for id: " + dataStore.getId());
152133
}
134+
String storagePoolUuid = dataStore.getUuid();
153135

154136
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(dataStore.getId());
155137

@@ -186,7 +168,7 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
186168

187169
// Create LUN-to-igroup mapping and retrieve the assigned LUN ID
188170
UnifiedSANStrategy sanStrategy = (UnifiedSANStrategy) Utility.getStrategyByStoragePoolDetails(details);
189-
String accessGroupName = Utility.getIgroupName(svmName, storagePool.getScope(), scopeId);
171+
String accessGroupName = Utility.getIgroupName(svmName, storagePoolUuid);
190172
String lunNumber = sanStrategy.ensureLunMapped(svmName, lunName, accessGroupName);
191173

192174
// Construct iSCSI path: /<iqn>/<lun_id> format for KVM/libvirt attachment
@@ -223,12 +205,7 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
223205
}
224206

225207
/**
226-
* Creates a CloudStack volume on the ONTAP backend using the appropriate storage strategy.
227-
*
228-
* @param dataStore The target data store
229-
* @param dataObject The volume to create
230-
* @param details Storage pool configuration details
231-
* @return CloudStackVolume containing the created backend object (LUN for iSCSI)
208+
* Creates a volume on the ONTAP backend.
232209
*/
233210
private CloudStackVolume createCloudStackVolume(DataStore dataStore, DataObject dataObject, Map<String, String> details) {
234211
StoragePoolVO storagePool = storagePoolDao.findById(dataStore.getId());
@@ -249,18 +226,7 @@ private CloudStackVolume createCloudStackVolume(DataStore dataStore, DataObject
249226
}
250227

251228
/**
252-
* Asynchronously deletes a volume from the ONTAP storage system.
253-
*
254-
* For iSCSI protocol:
255-
* - Retrieves LUN details from volume_details table
256-
* - Deletes the LUN from ONTAP (LUN mappings are automatically removed)
257-
*
258-
* For NFS protocol:
259-
* - No ONTAP operation needed; file deletion handled by KVM host/libvirt
260-
*
261-
* @param store The data store containing the volume
262-
* @param data The volume to delete
263-
* @param callback Callback to notify completion
229+
* Deletes a volume from the ONTAP storage system.
264230
*/
265231
@Override
266232
public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
@@ -344,20 +310,7 @@ public ChapInfo getChapInfo(DataObject dataObject) {
344310
}
345311

346312
/**
347-
* Grants a host access to a volume on the ONTAP storage system.
348-
*
349-
* For iSCSI protocol:
350-
* - Validates that the host's iSCSI initiator (IQN) is present in the target igroup
351-
* - Ensures the LUN is mapped to the igroup (creates mapping if not exists)
352-
* - Updates the volume's iSCSI path with the assigned LUN ID
353-
*
354-
* For NFS protocol:
355-
* - No explicit grant needed; NFS exports are configured at storage pool level
356-
*
357-
* @param dataObject The volume to grant access to
358-
* @param host The host requesting access
359-
* @param dataStore The data store containing the volume
360-
* @return true if access was granted successfully
313+
* Grants a host access to a volume.
361314
*/
362315
@Override
363316
public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
@@ -377,6 +330,7 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
377330
s_logger.error("grantAccess: Storage Pool not found for id: " + dataStore.getId());
378331
throw new CloudRuntimeException("grantAccess: Storage Pool not found for id: " + dataStore.getId());
379332
}
333+
String storagePoolUuid = dataStore.getUuid();
380334

381335
// ONTAP managed storage only supports cluster and zone scoped pools
382336
if (storagePool.getScope() != ScopeType.CLUSTER && storagePool.getScope() != ScopeType.ZONE) {
@@ -398,7 +352,7 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
398352

399353
if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
400354
UnifiedSANStrategy sanStrategy = (UnifiedSANStrategy) Utility.getStrategyByStoragePoolDetails(details);
401-
String accessGroupName = Utility.getIgroupName(svmName, storagePool.getScope(), scopeId);
355+
String accessGroupName = Utility.getIgroupName(svmName, storagePoolUuid);
402356

403357
// Verify host initiator is registered in the igroup before allowing access
404358
if (!sanStrategy.validateInitiatorInAccessGroup(host.getStorageUrl(), svmName, accessGroupName)) {
@@ -432,18 +386,7 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
432386
}
433387

434388
/**
435-
* Revokes a host's access to a volume on the ONTAP storage system.
436-
*
437-
* For iSCSI protocol:
438-
* - Validates the volume is not attached to an active VM
439-
* - Removes the LUN mapping from the igroup
440-
*
441-
* For NFS protocol:
442-
* - No explicit revoke needed; NFS exports remain at storage pool level
443-
*
444-
* @param dataObject The volume to revoke access from
445-
* @param host The host losing access
446-
* @param dataStore The data store containing the volume
389+
* Revokes a host's access to a volume.
447390
*/
448391
@Override
449392
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
@@ -467,7 +410,7 @@ public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
467410
VirtualMachine.State.Destroyed,
468411
VirtualMachine.State.Expunging,
469412
VirtualMachine.State.Error).contains(vm.getState())) {
470-
s_logger.debug("revokeAccess: Volume [{}] is still attached to VM [{}] in state [{}], skipping revokeAccess",
413+
s_logger.warn("revokeAccess: Volume [{}] is still attached to VM [{}] in state [{}], skipping revokeAccess",
471414
dataObject.getId(), vm.getInstanceName(), vm.getState());
472415
return;
473416
}
@@ -503,23 +446,19 @@ public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
503446
}
504447

505448
/**
506-
* Revokes volume access by removing the LUN mapping from the igroup.
507-
* This method handles the iSCSI-specific logic for access revocation.
508-
*
509-
* @param storagePool The storage pool containing the volume
510-
* @param volumeVO The volume to revoke access from
511-
* @param host The host losing access
449+
* Revokes volume access for the specified host.
512450
*/
513451
private void revokeAccessForVolume(StoragePoolVO storagePool, VolumeVO volumeVO, Host host) {
514452
s_logger.info("revokeAccessForVolume: Revoking access to volume [{}] for host [{}]", volumeVO.getName(), host.getName());
515453

516454
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId());
517455
StorageStrategy storageStrategy = Utility.getStrategyByStoragePoolDetails(details);
518456
String svmName = details.get(Constants.SVM_NAME);
457+
String storagePoolUuid = storagePool.getUuid();
519458
long scopeId = (storagePool.getScope() == ScopeType.CLUSTER) ? host.getClusterId() : host.getDataCenterId();
520459

521460
if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
522-
String accessGroupName = Utility.getIgroupName(svmName, storagePool.getScope(), scopeId);
461+
String accessGroupName = Utility.getIgroupName(svmName, storagePoolUuid);
523462

524463
// Retrieve LUN name from volume details; if missing, volume may not have been fully created
525464
String lunName = volumeDetailsDao.findDetail(volumeVO.getId(), Constants.LUN_DOT_NAME) != null ?
@@ -563,12 +502,7 @@ private void revokeAccessForVolume(StoragePoolVO storagePool, VolumeVO volumeVO,
563502
}
564503

565504
/**
566-
* Retrieves a CloudStack volume (LUN) from ONTAP by name.
567-
*
568-
* @param storageStrategy The storage strategy to use for the lookup
569-
* @param svmName The SVM name containing the LUN
570-
* @param cloudStackVolumeName The LUN name to look up
571-
* @return CloudStackVolume if found, null otherwise
505+
* Retrieves a volume from ONTAP by name.
572506
*/
573507
private CloudStackVolume getCloudStackVolumeByName(StorageStrategy storageStrategy, String svmName, String cloudStackVolumeName) {
574508
Map<String, String> getCloudStackVolumeMap = new HashMap<>();
@@ -584,12 +518,7 @@ private CloudStackVolume getCloudStackVolumeByName(StorageStrategy storageStrate
584518
}
585519

586520
/**
587-
* Retrieves an access group (igroup) from ONTAP by name.
588-
*
589-
* @param storageStrategy The storage strategy to use for the lookup
590-
* @param svmName The SVM name containing the igroup
591-
* @param accessGroupName The igroup name to look up
592-
* @return AccessGroup if found, null otherwise
521+
* Retrieves an access group from ONTAP by name.
593522
*/
594523
private AccessGroup getAccessGroupByName(StorageStrategy storageStrategy, String svmName, String accessGroupName) {
595524
Map<String, String> getAccessGroupMap = new HashMap<>();

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,21 @@ public DataStore initialize(Map<String, Object> dsInfos) {
152152
throw new CloudRuntimeException("ONTAP primary storage must be managed");
153153
}
154154

155-
// Required ONTAP detail keys
156155
Set<String> requiredKeys = Set.of(
157156
Constants.USERNAME,
158157
Constants.PASSWORD,
159158
Constants.SVM_NAME,
160159
Constants.PROTOCOL,
161-
Constants.MANAGEMENT_LIF,
160+
Constants.MANAGEMENT_LIF
161+
);
162+
163+
Set<String> optionalKeys = Set.of(
162164
Constants.IS_DISAGGREGATED
163165
);
164166

167+
Set<String> allowedKeys = new java.util.HashSet<>(requiredKeys);
168+
allowedKeys.addAll(optionalKeys);
169+
165170
// Parse key=value pairs from URL into details (skip empty segments)
166171
if (url != null && !url.isEmpty()) {
167172
for (String segment : url.split(Constants.SEMICOLON)) {
@@ -249,13 +254,13 @@ public DataStore initialize(Map<String, Object> dsInfos) {
249254
case NFS3:
250255
parameters.setType(Storage.StoragePoolType.NetworkFilesystem);
251256
path = Constants.SLASH + storagePoolName;
252-
port = 2049;
257+
port = Constants.NFS3_PORT;
253258
s_logger.info("Setting NFS path for storage pool: " + path + ", port: " + port);
254259
break;
255260
case ISCSI:
256261
parameters.setType(Storage.StoragePoolType.Iscsi);
257262
path = storageStrategy.getStoragePath();
258-
port = 3260;
263+
port = Constants.ISCSI_PORT;
259264
s_logger.info("Setting iSCSI path for storage pool: " + path + ", port: " + port);
260265
break;
261266
default:

0 commit comments

Comments
 (0)