Skip to content

Commit 45b634f

Browse files
Multi-Host support for NFS3 and iSCSI
1 parent 5af4bf9 commit 45b634f

7 files changed

Lines changed: 395 additions & 109 deletions

File tree

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

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,13 @@
1818
*/
1919
package org.apache.cloudstack.storage.driver;
2020

21-
import org.apache.cloudstack.storage.utils.OntapStorageConstants;
22-
import com.cloud.agent.api.Answer;
23-
import com.cloud.agent.api.to.DataObjectType;
24-
import com.cloud.agent.api.to.DataStoreTO;
25-
import com.cloud.agent.api.to.DataTO;
26-
import com.cloud.exception.InvalidParameterValueException;
27-
import com.cloud.host.Host;
28-
import com.cloud.host.HostVO;
29-
import com.cloud.storage.Storage;
30-
import com.cloud.storage.StoragePool;
31-
import com.cloud.storage.Volume;
32-
import com.cloud.storage.VolumeDetailVO;
33-
import com.cloud.storage.VolumeVO;
34-
import com.cloud.storage.ScopeType;
35-
import com.cloud.storage.dao.SnapshotDetailsDao;
36-
import com.cloud.storage.dao.SnapshotDetailsVO;
37-
import com.cloud.storage.dao.VolumeDao;
38-
import com.cloud.storage.dao.VolumeDetailsDao;
39-
import com.cloud.utils.Pair;
40-
import com.cloud.utils.exception.CloudRuntimeException;
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
26+
import javax.inject.Inject;
27+
4128
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
4229
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
4330
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
@@ -66,17 +53,36 @@
6653
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
6754
import org.apache.cloudstack.storage.service.model.ProtocolType;
6855
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
56+
import org.apache.cloudstack.storage.utils.OntapStorageConstants;
6957
import org.apache.cloudstack.storage.utils.OntapStorageUtils;
7058
import org.apache.commons.lang3.StringUtils;
7159
import org.apache.logging.log4j.LogManager;
7260
import org.apache.logging.log4j.Logger;
7361
import org.jetbrains.annotations.Nullable;
7462

75-
import javax.inject.Inject;
76-
import java.util.ArrayList;
77-
import java.util.HashMap;
78-
import java.util.List;
79-
import java.util.Map;
63+
import com.cloud.agent.api.Answer;
64+
import com.cloud.agent.api.to.DataObjectType;
65+
import com.cloud.agent.api.to.DataStoreTO;
66+
import com.cloud.agent.api.to.DataTO;
67+
import com.cloud.exception.InvalidParameterValueException;
68+
import com.cloud.exception.StorageConflictException;
69+
import com.cloud.exception.StorageUnavailableException;
70+
import com.cloud.host.Host;
71+
import com.cloud.host.HostVO;
72+
import com.cloud.storage.ScopeType;
73+
import com.cloud.storage.Storage;
74+
import com.cloud.storage.StorageManager;
75+
import com.cloud.storage.StoragePool;
76+
import com.cloud.storage.StoragePoolHostVO;
77+
import com.cloud.storage.Volume;
78+
import com.cloud.storage.VolumeDetailVO;
79+
import com.cloud.storage.VolumeVO;
80+
import com.cloud.storage.dao.SnapshotDetailsDao;
81+
import com.cloud.storage.dao.SnapshotDetailsVO;
82+
import com.cloud.storage.dao.VolumeDao;
83+
import com.cloud.storage.dao.VolumeDetailsDao;
84+
import com.cloud.utils.Pair;
85+
import com.cloud.utils.exception.CloudRuntimeException;
8086

8187
/**
8288
* Primary datastore driver for NetApp ONTAP storage systems.
@@ -91,6 +97,7 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
9197
@Inject private VolumeDao volumeDao;
9298
@Inject private VolumeDetailsDao volumeDetailsDao;
9399
@Inject private SnapshotDetailsDao snapshotDetailsDao;
100+
@Inject private StorageManager storageManager;
94101

95102
@Override
96103
public Map<String, String> getCapabilities() {
@@ -392,8 +399,9 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
392399
// Only retrieve LUN name for iSCSI volumes
393400
grantAccessIscsi(host, volumeVO, details, svmName, storagePool);
394401
} else if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(OntapStorageConstants.PROTOCOL))) {
395-
// For NFS, no access grant needed - file is accessible via mount
396-
logger.debug("grantAccess: NFS volume [{}], no igroup mapping required", volumeVO.getUuid());
402+
// For NFS, ensure export policy has host rule and host is connected to pool.
403+
ensureNfsHostAccess(host, storagePool, details);
404+
logger.debug("grantAccess: NFS volume [{}], ensured host access to storage pool [{}]", volumeVO.getUuid(), storagePool.getId());
397405
return true;
398406
}
399407
volumeVO.setPoolType(storagePool.getPoolType());
@@ -410,6 +418,43 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
410418
}
411419
}
412420

421+
private void ensureNfsHostAccess(Host host, StoragePoolVO storagePool, Map<String, String> details) {
422+
boolean hostConnectedToPool = false;
423+
List<StoragePoolHostVO> connectedPools = storageManager.findStoragePoolsConnectedToHost(host.getId());
424+
if (connectedPools != null) {
425+
for (StoragePoolHostVO connectedPoolRef : connectedPools) {
426+
if (connectedPoolRef.getPoolId() == storagePool.getId()) {
427+
hostConnectedToPool = true;
428+
break;
429+
}
430+
}
431+
}
432+
433+
if (!hostConnectedToPool) {
434+
try {
435+
logger.info("grantAccess: Host [{}] is not connected to NFS pool [{}], reconnecting host to pool", host.getId(), storagePool.getId());
436+
boolean connected = storageManager.connectHostToSharedPool(host, storagePool.getId());
437+
if (!connected) {
438+
throw new CloudRuntimeException("Failed to connect host " + host.getId() + " to NFS pool " + storagePool.getId());
439+
}
440+
} catch (StorageUnavailableException | StorageConflictException e) {
441+
throw new CloudRuntimeException("Unable to connect host " + host.getId() + " to NFS pool " + storagePool.getId(), e);
442+
}
443+
return;
444+
}
445+
446+
if (!(host instanceof HostVO)) {
447+
throw new CloudRuntimeException("Host object is not of type HostVO for host id: " + host.getId());
448+
}
449+
450+
AccessGroup accessGroup = new AccessGroup();
451+
accessGroup.setStoragePoolId(storagePool.getId());
452+
accessGroup.setHostsToConnect(List.of((HostVO) host));
453+
454+
StorageStrategy strategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);
455+
strategy.updateAccessGroup(accessGroup);
456+
}
457+
413458
private void grantAccessIscsi(Host host, VolumeVO volumeVO, Map<String, String> details, String svmName, StoragePoolVO storagePool) {
414459
String cloudStackVolumeName = volumeDetailsDao.findDetail(volumeVO.getId(), OntapStorageConstants.LUN_DOT_NAME).getValue();
415460
UnifiedSANStrategy sanStrategy = (UnifiedSANStrategy) OntapStorageUtils.getStrategyByStoragePoolDetails(details);

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919

2020
package org.apache.cloudstack.storage.feign.model;
2121

22+
import java.util.List;
23+
24+
import com.fasterxml.jackson.annotation.JsonCreator;
2225
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2326
import com.fasterxml.jackson.annotation.JsonInclude;
2427
import com.fasterxml.jackson.annotation.JsonProperty;
25-
import java.util.List;
28+
import com.fasterxml.jackson.annotation.JsonValue;
2629

2730
/**
2831
* ExportRule
@@ -54,6 +57,7 @@ public enum ProtocolsEnum {
5457
this.value = value;
5558
}
5659

60+
@JsonValue
5761
public String getValue() {
5862
return value;
5963
}
@@ -63,13 +67,17 @@ public String toString() {
6367
return String.valueOf(value);
6468
}
6569

70+
@JsonCreator
6671
public static ProtocolsEnum fromValue(String text) {
72+
if (text == null) {
73+
return null;
74+
}
6775
for (ProtocolsEnum b : ProtocolsEnum.values()) {
68-
if (String.valueOf(b.value).equals(text)) {
76+
if (String.valueOf(b.value).equalsIgnoreCase(text) || b.name().equalsIgnoreCase(text)) {
6977
return b;
7078
}
7179
}
72-
return null;
80+
throw new IllegalArgumentException("Unexpected protocol value '" + text + "'");
7381
}
7482
}
7583

0 commit comments

Comments
 (0)