Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,16 @@ protected Void managedCopyBaseImageCallback(AsyncCallbackDispatcher<VolumeServic
CopyCmdAnswer answer = (CopyCmdAnswer)result.getAnswer();
TemplateObjectTO templateObjectTo = (TemplateObjectTO)answer.getNewData();

volume.setPath(templateObjectTo.getPath());
// For NFS managed storage, preserve the volume UUID path to avoid file collision
// For iSCSI, update path as before (iSCSI uses _iScsiName field for actual LUN access)
PrimaryDataStore primaryDataStore = (PrimaryDataStore) volumeInfo.getDataStore();
if (primaryDataStore != null && primaryDataStore.getPoolType() == StoragePoolType.NetworkFilesystem) {
if (logger.isDebugEnabled()) {
logger.debug("NFS managed storage - preserving volume path: " + volume.getPath() + " (not overwriting with template path: " + templateObjectTo.getPath() + ")");
}
} else {
volume.setPath(templateObjectTo.getPath());
}

if (templateObjectTo.getFormat() != null) {
volume.setFormat(templateObjectTo.getFormat());
Expand Down Expand Up @@ -1337,6 +1346,14 @@ private void createManagedVolumeCopyTemplateAsync(VolumeInfo volumeInfo, Primary
primaryDataStore.setDetails(details);

grantAccess(volumeInfo, destHost, primaryDataStore);
volumeInfo = volFactory.getVolume(volumeInfo.getId(), primaryDataStore);
details.put(PrimaryDataStore.MANAGED_STORE_TARGET, volumeInfo.get_iScsiName());
primaryDataStore.setDetails(details);

// Update destTemplateInfo with the iSCSI path from volumeInfo
if (destTemplateInfo instanceof TemplateObject) {
((TemplateObject)destTemplateInfo).setInstallPath(volumeInfo.getPath());
}

try {
motionSrv.copyAsync(srcTemplateInfo, destTemplateInfo, destHost, caller);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.cloud.agent.api.to.DataTO;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.storage.Storage;
import com.cloud.storage.StoragePool;
import com.cloud.storage.Volume;
Expand All @@ -50,6 +51,7 @@
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.feign.model.Igroup;
import org.apache.cloudstack.storage.feign.model.Lun;
import org.apache.cloudstack.storage.service.SANStrategy;
import org.apache.cloudstack.storage.service.StorageStrategy;
Expand All @@ -63,8 +65,10 @@
import org.apache.logging.log4j.Logger;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -145,7 +149,6 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
volumeVO.setPoolId(storagePool.getId());

if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
String svmName = details.get(Constants.SVM_NAME);
String lunName = created != null && created.getLun() != null ? created.getLun().getName() : null;
if (lunName == null) {
throw new CloudRuntimeException("createAsync: Missing LUN name for volume " + volInfo.getId());
Expand All @@ -158,22 +161,21 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
volumeVO.setFolder(created.getLun().getUuid());
}

// Create LUN-to-igroup mapping and retrieve the assigned LUN ID
UnifiedSANStrategy sanStrategy = (UnifiedSANStrategy) Utility.getStrategyByStoragePoolDetails(details);
String accessGroupName = Utility.getIgroupName(svmName, storagePoolUuid);
String lunNumber = sanStrategy.ensureLunMapped(svmName, lunName, accessGroupName);

// Construct iSCSI path: /<iqn>/<lun_id> format for KVM/libvirt attachment
String iscsiPath = Constants.SLASH + storagePool.getPath() + Constants.SLASH + lunNumber;
volumeVO.set_iScsiName(iscsiPath);
volumeVO.setPath(iscsiPath);
s_logger.info("createAsync: Volume [{}] iSCSI path set to {}", volumeVO.getId(), iscsiPath);
createCmdResult = new CreateCmdResult(null, new Answer(null, true, null));
s_logger.info("createAsync: Created LUN [{}] for volume [{}]. LUN mapping will occur during grantAccess() to per-host igroup.",
lunName, volumeVO.getId());

// Path will be set during grantAccess when LUN is mapped and we get the LUN ID
// Return LUN name as identifier for CloudStack tracking
volumeVO.set_iScsiName(null);
volumeVO.setPath(null);
createCmdResult = new CreateCmdResult(lunName, new Answer(null, true, null));
} else if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
// For NFS, set path to volume UUID to ensure uniqueness
// This prevents multiple VMs from using the same template file path
volumeVO.setPath(volInfo.getUuid());
createCmdResult = new CreateCmdResult(volInfo.getUuid(), new Answer(null, true, null));
s_logger.info("createAsync: Managed NFS volume [{}] associated with pool {}",
volumeVO.getId(), storagePool.getId());
s_logger.info("createAsync: Managed NFS volume [{}] with path [{}] associated with pool {}",
volumeVO.getId(), volInfo.getUuid(), storagePool.getId());
}
volumeDao.update(volumeVO.getId(), volumeVO);
}
Expand Down Expand Up @@ -319,12 +321,33 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore
// Only retrieve LUN name for iSCSI volumes
String cloudStackVolumeName = volumeDetailsDao.findDetail(volumeVO.getId(), Constants.LUN_DOT_NAME).getValue();
UnifiedSANStrategy sanStrategy = (UnifiedSANStrategy) Utility.getStrategyByStoragePoolDetails(details);
String accessGroupName = Utility.getIgroupName(svmName, storagePoolUuid);

// Verify host initiator is registered in the igroup before allowing access
if (!sanStrategy.validateInitiatorInAccessGroup(host.getStorageUrl(), svmName, accessGroupName)) {
throw new CloudRuntimeException("grantAccess: Host initiator [" + host.getStorageUrl() +
"] is not present in iGroup [" + accessGroupName + "]");
String accessGroupName = Utility.getIgroupName(svmName, host.getName());

// Validate if Igroup exist ONTAP for this host as we may be using delete_on_unmap= true and igroup may be deleted by ONTAP automatically
Map<String, String> getAccessGroupMap = Map.of(
Constants.NAME, accessGroupName,
Constants.SVM_DOT_NAME, svmName
);
Igroup igroup = new Igroup();
AccessGroup accessGroup = sanStrategy.getAccessGroup(getAccessGroupMap);
if(accessGroup == null || accessGroup.getIgroup() == null) {
s_logger.info("grantAccess: Igroup does not exist for the host: Need to create Igroup " + host.getName());
// create the igroup for the host and perform lun-mapping
accessGroup = new AccessGroup();
List<HostVO> hosts = new ArrayList<>();
hosts.add((HostVO) host);
accessGroup.setHostsToConnect(hosts);
accessGroup.setStoragePoolId(storagePool.getId());
accessGroup = sanStrategy.createAccessGroup(accessGroup);
}else{
s_logger.info("grantAccess: Igroup {} already exist for the host {}: ", accessGroup.getIgroup().getName() ,host.getName());
igroup = accessGroup.getIgroup();
// TODO
// Verify host initiator is registered in the igroup before allowing access
// if (sanStrategy.validateInitiatorInAccessGroup(host.getStorageUrl(), svmName, accessGroup.getIgroup())) {
// // add host initiator to the igroup ? or fail here ?
// }
// Use the existing igroup and perform lun-mapping
}

// Create or retrieve existing LUN mapping
Expand Down Expand Up @@ -453,7 +476,7 @@ private void revokeAccessForVolume(StoragePoolVO storagePool, VolumeVO volumeVO,

// Verify host initiator is in the igroup before attempting to remove mapping
SANStrategy sanStrategy = (UnifiedSANStrategy) storageStrategy;
if (!sanStrategy.validateInitiatorInAccessGroup(host.getStorageUrl(), svmName, accessGroup.getIgroup().getName())) {
if (!sanStrategy.validateInitiatorInAccessGroup(host.getStorageUrl(), svmName, accessGroup.getIgroup())) {
s_logger.warn("revokeAccessForVolume: Initiator [{}] is not in iGroup [{}], skipping revoke",
host.getStorageUrl(), accessGroupName);
return;
Expand Down Expand Up @@ -524,7 +547,6 @@ public long getUsedIops(StoragePool storagePool) {

@Override
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {

}

@Override
Expand Down Expand Up @@ -569,7 +591,6 @@ public boolean isVmInfoNeeded() {

@Override
public void provideVmInfo(long vmId, long volumeId) {

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public DataStore initialize(Map<String, Object> dsInfos) {
throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage");
}

if (podId == null && clusterId == null) {
if (podId == null) {
if (zoneId != null) {
s_logger.info("Both Pod Id and Cluster Id are null, Primary storage pool will be associated with a Zone");
} else {
Expand Down Expand Up @@ -231,7 +231,7 @@ public DataStore initialize(Map<String, Object> dsInfos) {
path = Constants.SLASH + storagePoolName;
port = Constants.NFS3_PORT;
// Force NFSv3 for ONTAP managed storage to avoid NFSv4 ID mapping issues
details.put("nfsmountopts", "vers=3");
details.put(Constants.NFS_MOUNT_OPTIONS,Constants.NFS3_MOUNT_OPTIONS_VER_3);
s_logger.info("Setting NFS path for storage pool: " + path + ", port: " + port + " with mount option: vers=3");
break;
case ISCSI:
Expand Down Expand Up @@ -281,8 +281,11 @@ public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
}
PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)dataStore;
List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInClusterForStorageConnection(primaryStore);
// TODO- need to check if no host to connect then throw exception or just continue?
logger.debug("attachCluster: Eligible Up and Enabled hosts: {} in cluster {}", hostsToConnect, primaryStore.getClusterId());
if(hostsToConnect.isEmpty()) {
s_logger.info("attachCluster: No hosts found for primary storage");
throw new CloudRuntimeException("attachCluster: No hosts found for primary storage");
}

Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(primaryStore.getId());
StorageStrategy strategy = Utility.getStrategyByStoragePoolDetails(details);
Expand All @@ -294,22 +297,24 @@ public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}

logger.debug("attachCluster: Attaching the pool to each of the host in the cluster: {}", primaryStore.getClusterId());
//TODO - check if no host to connect then also need to create access group without initiators
if (hostsIdentifier != null && hostsIdentifier.size() > 0) {
try {
AccessGroup accessGroupRequest = new AccessGroup();
accessGroupRequest.setHostsToConnect(hostsToConnect);
accessGroupRequest.setScope(scope);
primaryStore.setDetails(details);// setting details as it does not come from cloudstack
accessGroupRequest.setPrimaryDataStoreInfo(primaryStore);
strategy.createAccessGroup(accessGroupRequest);
} catch (Exception e) {
s_logger.error("attachCluster: Failed to create access group on storage system for cluster: " + primaryStore.getClusterId() + ". Exception: " + e.getMessage());
throw new CloudRuntimeException("attachCluster: Failed to create access group on storage system for cluster: " + primaryStore.getClusterId() + ". Exception: " + e.getMessage());
// We need to create export policy at pool level and igroup at host level(in grantAccess)
if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
if (!hostsIdentifier.isEmpty()) {
try {
AccessGroup accessGroupRequest = new AccessGroup();
accessGroupRequest.setHostsToConnect(hostsToConnect);
accessGroupRequest.setScope(scope);
primaryStore.setDetails(details);// setting details as it does not come from cloudstack
accessGroupRequest.setStoragePoolId(storagePool.getId());
strategy.createAccessGroup(accessGroupRequest);
} catch (Exception e) {
s_logger.error("attachCluster: Failed to create access group on storage system for cluster: " + primaryStore.getClusterId() + ". Exception: " + e.getMessage());
throw new CloudRuntimeException("attachCluster: Failed to create access group on storage system for cluster: " + primaryStore.getClusterId() + ". Exception: " + e.getMessage());
}
}
}

logger.debug("attachCluster: Attaching the pool to each of the host in the cluster: {}", primaryStore.getClusterId());
for (HostVO host : hostsToConnect) {
try {
Expand Down Expand Up @@ -347,30 +352,36 @@ public boolean attachZone(DataStore dataStore, ZoneScope scope, Hypervisor.Hyper
PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)dataStore;
List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInZoneForStorageConnection(dataStore, scope.getScopeId(), Hypervisor.HypervisorType.KVM);
logger.debug(String.format("In createPool. Attaching the pool to each of the hosts in %s.", hostsToConnect));
if(hostsToConnect.isEmpty()) {
s_logger.info("attachCluster: No hosts found for primary storage");
throw new CloudRuntimeException("attachCluster: No hosts found for primary storage");
}

Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(primaryStore.getId());
StorageStrategy strategy = Utility.getStrategyByStoragePoolDetails(details);

// TODO- need to check if no host to connect then throw exception or just continue
logger.debug("attachZone: Eligible Up and Enabled hosts: {}", hostsToConnect);
ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL));
//TODO- Check if we have to handle heterogeneous host within the zone
if (!validateProtocolSupportAndFetchHostsIdentifier(hostsToConnect, protocol, hostsIdentifier)) {
String errMsg = "attachZone: Not all hosts in the zone support the protocol: " + protocol.name();
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
if (hostsIdentifier != null && !hostsIdentifier.isEmpty()) {
try {
AccessGroup accessGroupRequest = new AccessGroup();
accessGroupRequest.setHostsToConnect(hostsToConnect);
accessGroupRequest.setScope(scope);
primaryStore.setDetails(details); // setting details as it does not come from cloudstack
accessGroupRequest.setPrimaryDataStoreInfo(primaryStore);
strategy.createAccessGroup(accessGroupRequest);
} catch (Exception e) {
s_logger.error("attachZone: Failed to create access group on storage system for zone with Exception: " + e.getMessage());
throw new CloudRuntimeException("attachZone: Failed to create access group on storage system for zone with Exception: " + e.getMessage());

// We need to create export policy at pool level and igroup at host level
if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
if (!hostsIdentifier.isEmpty()) {
try {
AccessGroup accessGroupRequest = new AccessGroup();
accessGroupRequest.setHostsToConnect(hostsToConnect);
accessGroupRequest.setScope(scope);
primaryStore.setDetails(details); // setting details as it does not come from cloudstack
accessGroupRequest.setStoragePoolId(storagePool.getId());
strategy.createAccessGroup(accessGroupRequest);
} catch (Exception e) {
s_logger.error("attachZone: Failed to create access group on storage system for zone with Exception: " + e.getMessage());
throw new CloudRuntimeException("attachZone: Failed to create access group on storage system for zone with Exception: " + e.getMessage());
}
}
}
for (HostVO host : hostsToConnect) {
Expand Down Expand Up @@ -485,7 +496,7 @@ public boolean deleteDataStore(DataStore store) {
storagePoolId, e.getMessage(), e);
}
AccessGroup accessGroup = new AccessGroup();
accessGroup.setPrimaryDataStoreInfo(primaryDataStoreInfo);
accessGroup.setStoragePoolId(storagePoolId);
// Delete access groups associated with this storage pool
storageStrategy.deleteAccessGroup(accessGroup);
s_logger.info("deleteDataStore: Successfully deleted access groups for storage pool '{}'", storagePool.getName());
Expand Down
Loading
Loading