From 3bae3926a150448a91f7600ade1c774d6010e085 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Tue, 19 May 2026 13:15:00 -0400 Subject: [PATCH] Add support to update secondary and primary storage URLs --- .../admin/storage/UpdateImageStoreCmd.java | 11 +++++++ .../com/cloud/storage/StorageManager.java | 2 +- .../java/com/cloud/server/StatsCollector.java | 2 +- .../com/cloud/storage/StorageManagerImpl.java | 32 +++++++++++++++++-- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateImageStoreCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateImageStoreCmd.java index 0e1631a46ba2..ee83b78d07fe 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateImageStoreCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateImageStoreCmd.java @@ -50,6 +50,13 @@ public class UpdateImageStoreCmd extends BaseCmd { description = "The number of bytes CloudStack can use on this image storage.\n\tNOTE: this will be overwritten by the StatsCollector as soon as there is a SSVM to query the storage.") private Long capacityBytes; + @Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = false, + description = "The new URL for the image store (e.g. nfs://new-host/export). " + + "The image store must be in read-only state before its URL can be changed. " + + "After updating, destroy and recreate any Secondary Storage VMs so they remount the new path.", + since = "4.23.0") + private String url; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -70,6 +77,10 @@ public Long getCapacityBytes() { return capacityBytes; } + public String getUrl() { + return url; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index 3c62738f9ed5..455ac8c33389 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -411,7 +411,7 @@ void connectHostsToPool(DataStore primaryStore, List hostIds, Scope scope, Long getDiskIopsWriteRate(ServiceOffering offering, DiskOffering diskOffering); - ImageStore updateImageStoreStatus(Long id, String name, Boolean readonly, Long capacityBytes); + ImageStore updateImageStoreStatus(Long id, String name, Boolean readonly, Long capacityBytes, String url); void cleanupDownloadUrls(); diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index 049ed4543617..5a98feac51a2 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -1787,7 +1787,7 @@ private void updateStorageStats(ConcurrentHashMap storageSta && (_storageStats.get(storeId).getCapacityBytes() == 0l || _storageStats.get(storeId).getCapacityBytes() != storageStats.get(storeId).getCapacityBytes())) { // get add to DB rigorously - _storageManager.updateImageStoreStatus(storeId, null, null, storageStats.get(storeId).getCapacityBytes()); + _storageManager.updateImageStoreStatus(storeId, null, null, storageStats.get(storeId).getCapacityBytes(), null); } } // if in _storageStats and not in storageStats it gets discarded diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 7c501c78beeb..f62df0585ae1 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1285,6 +1285,10 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I changes = true; } + if (cmd.getUrl() != null) { + changes = true; + } + if (changes) { StoragePoolVO storagePool = _storagePoolDao.findById(id); DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(storagePool.getStorageProviderName()); @@ -1300,6 +1304,21 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I _storagePoolDao.updateCapacityIops(id, updatedCapacityIops); } if (cmd.getUrl() != null) { + if (!storagePool.isInMaintenance()) { + throw new InvalidParameterValueException("Storage pool must be in Maintenance state before its URL can be changed. " + + "Please put the pool into maintenance first."); + } + URI newUri; + try { + newUri = new URI(cmd.getUrl()); + } catch (URISyntaxException e) { + throw new InvalidParameterValueException("Invalid URL format: " + cmd.getUrl()); + } + storagePool.setHostAddress(newUri.getHost()); + storagePool.setPath(newUri.getPath()); + if (newUri.getPort() != -1) { + storagePool.setPort(newUri.getPort()); + } details.put("url", cmd.getUrl()); } _storagePoolDao.update(id, storagePool); @@ -4129,18 +4148,25 @@ public ImageStore migrateToObjectStore(String name, String url, String providerN @Override public ImageStore updateImageStore(UpdateImageStoreCmd cmd) { - return updateImageStoreStatus(cmd.getId(), cmd.getName(), cmd.getReadonly(), cmd.getCapacityBytes()); + return updateImageStoreStatus(cmd.getId(), cmd.getName(), cmd.getReadonly(), cmd.getCapacityBytes(), cmd.getUrl()); } @Override @ActionEvent(eventType = EventTypes.EVENT_UPDATE_IMAGE_STORE_ACCESS_STATE, eventDescription = "image store access updated") - public ImageStore updateImageStoreStatus(Long id, String name, Boolean readonly, Long capacityBytes) { + public ImageStore updateImageStoreStatus(Long id, String name, Boolean readonly, Long capacityBytes, String url) { // Input validation ImageStoreVO imageStoreVO = _imageStoreDao.findById(id); if (imageStoreVO == null) { throw new IllegalArgumentException("Unable to find image store with ID: " + id); } + if (url != null) { + if (!imageStoreVO.isReadonly()) { + throw new InvalidParameterValueException("Image store must be set to read-only (maintenance) state before its URL can be changed. " + + "Please set readOnly=true on the image store first."); + } + imageStoreVO.setUrl(url); + } if (com.cloud.utils.StringUtils.isNotBlank(name)) { imageStoreVO.setName(name); } @@ -4156,7 +4182,7 @@ public ImageStore updateImageStoreStatus(Long id, String name, Boolean readonly, @Override public ImageStore updateImageStoreStatus(Long id, Boolean readonly) { - return updateImageStoreStatus(id, null, readonly, null); + return updateImageStoreStatus(id, null, readonly, null, null); } /**