-
Notifications
You must be signed in to change notification settings - Fork 0
feature/CSTACKEX-122: Per host Igroup changes #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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() + ")"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it already in Debug mode? Do we need to check for it specifically? |
||
| } | ||
| } else { | ||
| volume.setPath(templateObjectTo.getPath()); | ||
| } | ||
|
|
||
| if (templateObjectTo.getFormat() != null) { | ||
| volume.setFormat(templateObjectTo.getFormat()); | ||
|
|
@@ -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); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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; | ||
|
|
@@ -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; | ||
|
|
||
| /** | ||
|
|
@@ -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()); | ||
|
|
@@ -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); | ||
| } | ||
|
|
@@ -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 ? | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are 2 corner cases that I see this happening:
|
||
| // } | ||
| // Use the existing igroup and perform lun-mapping | ||
| } | ||
|
|
||
| // Create or retrieve existing LUN mapping | ||
|
|
@@ -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; | ||
|
|
@@ -524,7 +547,6 @@ public long getUsedIops(StoragePool storagePool) { | |
|
|
||
| @Override | ||
| public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) { | ||
|
|
||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -569,7 +591,6 @@ public boolean isVmInfoNeeded() { | |
|
|
||
| @Override | ||
| public void provideVmInfo(long vmId, long volumeId) { | ||
|
|
||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
|
@@ -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: | ||
|
|
@@ -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()) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to enforce having hosts before creating a pool? Anyways hostlistener will handle the necessary operations in case if new hosts got added right? |
||
| 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); | ||
|
|
@@ -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 { | ||
|
|
@@ -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) { | ||
|
|
@@ -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()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a common path. Please add the plugin check as well, since we want this applied to the ONTAP flow rather than all the vendors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if plugin name would be available in this method. If its not available, we can check if volume.getPath is null and set it if there's new info available.