From 3019bece89c59df1dd51c1b04e3bc4b8c64af33f Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sun, 22 Feb 2026 00:52:14 +0530 Subject: [PATCH 1/2] storage: validate NFS secondary storage mount using SSVM during image store discovery --- .../com/cloud/storage/StorageManagerImpl.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 9f9928bfb663..e16ae48c13b4 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -276,6 +276,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.google.common.collect.Sets; @@ -344,6 +345,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C @Inject protected VolumeDao volumeDao; @Inject + protected SecondaryStorageVmManager _ssVmMgr; + @Inject ConfigurationDao _configDao; @Inject ManagementServer _msServer; @@ -3990,6 +3993,37 @@ public ImageStore discoverImageStore(String name, String url, String providerNam throw new CloudRuntimeException("Failed to add data store: " + e.getMessage(), e); } + // Validate secondary storage mount immediately using SSVM +if (zoneId != null) { + List ssvmHosts = _hostDao.listByType(Host.Type.SecondaryStorageVM); + boolean mountSuccess = false; + String failureReason = "No Secondary Storage VM available to validate the NFS mount."; + + for (HostVO ssvm : ssvmHosts) { + if (ssvm.getDataCenterId() != zoneId.longValue()) { + continue; + } + + try { + boolean result = _ssVmMgr.generateSetupCommand(ssvm.getId()); + if (result) { + mountSuccess = true; + break; + } else { + failureReason = "Secondary Storage VM failed to mount the NFS secondary storage."; + } + } catch (Exception e) { + failureReason = e.getMessage(); + } + } + + if (!mountSuccess) { + // cleanup created store + _imageStoreDao.remove(store.getId()); + throw new CloudRuntimeException("Invalid secondary storage mount: " + failureReason); + } +} + if (((ImageStoreProvider)storeProvider).needDownloadSysTemplate()) { // trigger system vm template download _imageSrv.downloadBootstrapSysTemplate(store); From 174b37d25b404da82fc81f15b484a737130fe1c4 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Tue, 24 Feb 2026 18:46:38 +0530 Subject: [PATCH 2/2] Fix SSVM validation and rollback lifecycle handling for NFS secondary store (CLOUDSTACK-12674) --- .../com/cloud/storage/StorageManagerImpl.java | 117 +++++++++++++----- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index e16ae48c13b4..5d6e04bf2d02 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -148,6 +148,7 @@ import org.apache.cloudstack.storage.object.ObjectStore; import org.apache.cloudstack.storage.object.ObjectStoreEntity; import org.apache.cloudstack.storage.to.VolumeObjectTO; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.time.DateUtils; @@ -173,6 +174,9 @@ import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.manager.Commands; +import com.cloud.agent.api.SecStorageSetupCommand; +import com.cloud.agent.api.SecStorageSetupAnswer; +import com.cloud.agent.api.to.NfsTO; import com.cloud.api.ApiDBUtils; import com.cloud.api.query.dao.TemplateJoinDao; import com.cloud.api.query.vo.TemplateJoinVO; @@ -3992,37 +3996,7 @@ public ImageStore discoverImageStore(String name, String url, String providerNam } throw new CloudRuntimeException("Failed to add data store: " + e.getMessage(), e); } - - // Validate secondary storage mount immediately using SSVM -if (zoneId != null) { - List ssvmHosts = _hostDao.listByType(Host.Type.SecondaryStorageVM); - boolean mountSuccess = false; - String failureReason = "No Secondary Storage VM available to validate the NFS mount."; - - for (HostVO ssvm : ssvmHosts) { - if (ssvm.getDataCenterId() != zoneId.longValue()) { - continue; - } - - try { - boolean result = _ssVmMgr.generateSetupCommand(ssvm.getId()); - if (result) { - mountSuccess = true; - break; - } else { - failureReason = "Secondary Storage VM failed to mount the NFS secondary storage."; - } - } catch (Exception e) { - failureReason = e.getMessage(); - } - } - - if (!mountSuccess) { - // cleanup created store - _imageStoreDao.remove(store.getId()); - throw new CloudRuntimeException("Invalid secondary storage mount: " + failureReason); - } -} + validateSecondaryStorageMount(zoneId, (ImageStoreVO) store); if (((ImageStoreProvider)storeProvider).needDownloadSysTemplate()) { // trigger system vm template download @@ -4045,6 +4019,70 @@ public ImageStore discoverImageStore(String name, String url, String providerNam return (ImageStore)_dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Image); } + private void validateSecondaryStorageMount(Long zoneId, ImageStoreVO store) { + + if (zoneId == null) { + return; + } + + List ssvmHosts = _ssVmMgr.listUpAndConnectingSecondaryStorageVmHost(zoneId); + + String failureReason = "No active Secondary Storage VM available in zone to validate the NFS mount."; + + if (ssvmHosts == null || ssvmHosts.isEmpty()) { + cleanupImageStore(store.getId(), store.getUuid()); + throw new CloudRuntimeException(failureReason); + } + + boolean mountSuccess = false; + +for (HostVO ssvm : ssvmHosts) { + try { + DataStore dataStore = _dataStoreMgr.getDataStore(store.getId(), DataStoreRole.Image); + + if (!(dataStore.getTO() instanceof NfsTO)) { + continue; + } + + String secUrl = dataStore.getUri(); + SecStorageSetupCommand setupCmd = + new SecStorageSetupCommand(dataStore.getTO(), secUrl, null); + + String nfsVersion = imageStoreDetailsUtil.getNfsVersion(store.getId()); + setupCmd.setNfsVersion(nfsVersion); + + String postUploadKey = _configDao.getValue(Config.SSVMPSK.key()); + setupCmd.setPostUploadKey(postUploadKey); + + Answer answer = _agentMgr.easySend(ssvm.getId(), setupCmd); + + if (answer != null && answer.getResult()) { + + SecStorageSetupAnswer an = (SecStorageSetupAnswer) answer; + if (an.get_dir() != null) { + store.setParent(an.get_dir()); + _imageStoreDao.update(store.getId(), store); + } + + mountSuccess = true; + break; + } else { + failureReason = (answer == null) ? + "Null response from SSVM" : + answer.getDetails(); + } + + } catch (Exception e) { + failureReason = e.getMessage(); + } +} + + if (!mountSuccess) { + cleanupImageStore(store.getId(), store.getUuid()); + throw new CloudRuntimeException("Invalid secondary storage mount: " + failureReason); + } +} + protected void registerSystemVmTemplateForHypervisorArch(final HypervisorType hypervisorType, final CPU.CPUArch arch, final Long zoneId, final String url, final DataStore store, final SystemVmTemplateRegistration systemVmTemplateRegistration, final String filePath, @@ -4338,6 +4376,23 @@ public void doInTransactionWithoutResult(TransactionStatus status) { return true; } + private void cleanupImageStore(long storeId, String storeUuid) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + _imageStoreDetailsDao.deleteDetails(storeId); + _snapshotStoreDao.deletePrimaryRecordsForStore(storeId, DataStoreRole.Image); + _volumeStoreDao.deletePrimaryRecordsForStore(storeId); + _templateStoreDao.deletePrimaryRecordsForStore(storeId); + annotationDao.removeByEntityType( + AnnotationService.EntityType.SECONDARY_STORAGE.name(), + storeUuid + ); + _imageStoreDao.remove(storeId); + } + }); + } + @Override public ImageStore createSecondaryStagingStore(CreateSecondaryStagingStoreCmd cmd) { String providerName = cmd.getProviderName();