Skip to content

Multi-Host support for NFS3 and iSCSI#64

Open
sandeeplocharla wants to merge 4 commits into
mainfrom
feature/CSTACKEX-192
Open

Multi-Host support for NFS3 and iSCSI#64
sandeeplocharla wants to merge 4 commits into
mainfrom
feature/CSTACKEX-192

Conversation

@sandeeplocharla

@sandeeplocharla sandeeplocharla commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR...
Provides support to Multi-Host for NFS3 and iSCSI

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

How Has This Been Tested?

Test Done Result Comments
Create an instance by specifying the host and disk offering pointed to NFS3 Primary Storage Pool (Cluster scoped with min of 2 hosts) PASS  
Create an instance specifying host and disk offering pointed to iSCSI Primary Storage Pool (Cluster scoped with min of 2 hosts) PASS  
Create an instance in a cluster with atleast 2 hosts in the cluster by not specifying the host and disk offering pointed to NFS3 Primary Storage Pool (Cluster scoped with min of 2 hosts) PASS  
Create an instance in a cluster with atleast 2 hosts in the cluster by not specifying the host and disk offering pointed to iSCSI Primary Storage Pool (Cluster scoped with min of 2 hosts) PASS  
Create an instance by specifying the host and disk offering pointed to 2 NFS3 Primary Storage Pools (Cluster scoped + Zone scoped) FAIL StoragePoolAllocator is coming as empty and the random strategy to allocate was failing.
Create an instance by specifying the host and disk offering pointed to 2 iSCSI Primary Storage Pools (Cluster scoped + Zone scoped) FAIL StoragePoolAllocator is coming as empty and the random strategy to allocate was failing.
Create an instance by specifying the host and disk offering pointed to NFS3 Primary Storage Pool (Zone scoped with min of 2 hosts) FAIL Zone scoped instance creation failing (CSTACKEX-188)
Create an instance specifying host and disk offering pointed to iSCSI Primary Storage Pool (Zone scoped with min of 2 hosts) FAIL Zone scoped instance creation failing (CSTACKEX-188)
Create an instance in a cluster with atleast 2 hosts in the cluster by not specifying the host and disk offering pointed to NFS3 Primary Storage Pool (Zone scoped with min of 2 hosts) FAIL Zone scoped instance creation failing (CSTACKEX-188)
Create an instance in a cluster with atleast 2 hosts in the cluster by not specifying the host and disk offering pointed to iSCSI Primary Storage Pool (Zone scoped with min of 2 hosts) FAIL Zone scoped instance creation failing (CSTACKEX-188)
Power off VM on Host-1 and start it on Host-2 (same cluster) with disk offering pointed to NFS3 primary storage pool (cluster scoped) PASS  
Power off and on a VM with 'last known host' selected. Create the instance in a cluster, without specifying a host. PASS Last known host was selected
Power off and on a VM with 'last known host' NOT selected. Create the instance in a cluster, without specifying a host. PASS VM got hosted on the chosen host.
Power off and on a VM with 'last known host' selected but choose a different host. Create the instance in a cluster, without specifying a host. PASS VM got hosted on the chosen host.
Live migrate a VM between two hosts in the same cluster PASS  
Live migrate a VM between two hosts in different clusters with disk offering pointed to Zone scoped Primary Storage Pool FAIL Zone scoped instance creation failing (CSTACKEX-188)Couldn't try migration.
Insufficient resources in the storage pool. Instance creation should fail. PASS Generic error is being displayed instead of proper error regarding Insufficient resources.
One host in the cluster (min. 2 hosts) loaded, resulting in insufficient CPU. Disk Offering pointed to Cluster scoped primary storage pool. PASS  
Both host in the cluster (min. 2 hosts) loaded, resulting in insufficient CPU. Disk Offering pointed to Cluster scoped primary storage pool. Instance creation should fail but disks should first get created and then destroyed.

if (!hostConnectedToPool) {
try {
logger.info("grantAccess: Host [{}] is not connected to NFS pool [{}], reconnecting host to pool", host.getId(), storagePool.getId());
boolean connected = storageManager.connectHostToSharedPool(host, storagePool.getId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shared pool term is picked from the CloudStack perspective, or from the way we are approaching storage pool design. Please add code comment, if possible

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'm aware that this code is a bit of a stretch, I have added with nfs protocol being enabled on a host post its addition to the storage pool. I wasn't able to simulate the scenario and looks like a rare occurence. So, on discussing with the team in the scrum today, we came to consensus that at this point we can document the pre-requisite of enabling the protocol on host and remove this code. I wanted to take your opinion on the same. So, please let me know if you feel the same?

@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch.

StoragePool pool = _storagePoolDao.findById(poolId);
if (pool == null) {
logger.error("Failed to disconnect host - storage pool not found with id: {}", poolId);
if (!host.getHypervisorType().equals(Hypervisor.HypervisorType.KVM)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this logic is executed during host removal, and the host is not a KVM host, do we really need to return false? Would it make more sense to return true instead?

Returning false could potentially block the host removal workflow, which may not be desirable and does not appear to provide any benefit to our plugin. Could you please confirm the expected behavior here and whether returning false is intentionally required for non-KVM hosts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the host is not KVM, the call shouldn't even come our plugin's host listener. So, this check seems redundant right now. Removing it.

Copilot AI review requested due to automatic review settings July 8, 2026 05:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds multi-host lifecycle handling for the ONTAP volume plugin by updating NFS export policy rules as hosts connect to / are removed from storage pools, and by extending the AccessGroup model/strategy API to support add/remove semantics.

Changes:

  • Implemented UnifiedNASStrategy.updateAccessGroup to add/remove host client matches in an existing NFS export policy.
  • Added HostRuleAction to AccessGroup and wired host-connect / host-removal flows to update NFS3 export policy rules.
  • Tightened/adjusted model and API surface (e.g., StorageStrategy.updateAccessGroup visibility; ExportRule.ProtocolsEnum JSON annotations).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/StorageStrategyTest.java Updates test scaffolding to match new abstract method visibility; import/method formatting adjustments.
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java Implements export-policy mutation logic for NFS host add/remove (multi-host enablement).
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java Makes updateAccessGroup explicitly public abstract for consistent overriding/call sites.
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/AccessGroup.java Adds HostRuleAction (ADD/REMOVE) to drive update semantics.
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/listener/OntapHostListener.java Updates host lifecycle handling to update NFS3 export policy rules on connect and before removal.
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java Adjusts enum JSON serialization/deserialization behavior for export rule protocols.
plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java Import reordering / formatting-only change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 161 to 163
@Override
AccessGroup updateAccessGroup(AccessGroup accessGroup) {
public AccessGroup updateAccessGroup(AccessGroup accessGroup) {
return null;
Comment on lines 45 to +56
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import org.mockito.Mock;
Comment on lines +188 to +191
HostVO hostVO = (HostVO) host;
if (!isNfs3EnabledOnHost(hostVO)) {
throw new CloudRuntimeException("NFS protocol is not enabled on host with id: " + hostId);
}
Comment on lines +228 to +232
Host host = _hostDao.findById(hostId);
if (host == null) {
logger.warn("Host not found with id: {}", hostId);
return false;
}
Comment on lines +175 to +178
private void updateNfsExportPolicyForConnectedHostIfNeeded(long poolId, long hostId, Host host, Map<String, String> detailsMap) {
if (!ProtocolType.NFS3.name().equalsIgnoreCase(detailsMap.get(OntapStorageConstants.PROTOCOL))) {
return;
}
}
}
return null;
throw new IllegalArgumentException("Unexpected protocol value '" + text + "'");
Comment on lines 198 to +205
public AccessGroup updateAccessGroup(AccessGroup accessGroup) {
return null;
if (accessGroup == null) {
throw new CloudRuntimeException("Invalid accessGroup object - accessGroup is null");
}
// Check if an AccessGroup was constructed without associating it to a storage pool.
if (accessGroup.getStoragePoolId() == null) {
throw new CloudRuntimeException("Invalid accessGroup object - storagePoolId is null");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants