From dd1844cc5c2ac19a7ea853816012926d5200df70 Mon Sep 17 00:00:00 2001 From: sr73318 Date: Fri, 7 Aug 2026 15:11:09 +0530 Subject: [PATCH 1/6] CSTACKEX-234: Enabling storage pool resize (grow and shrink) --- .../OntapPrimaryDatastoreLifecycle.java | 30 +++++++++++++++++++ .../storage/service/StorageStrategy.java | 23 +++++++++++--- .../com/cloud/storage/StorageManagerImpl.java | 2 +- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index a206eaa053df..59852aa9bca9 100755 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -526,7 +526,37 @@ public boolean migrateToObjectStore(DataStore store) { @Override public void updateStoragePool(StoragePool storagePool, Map details) { + StoragePoolVO poolVO = storagePoolDao.findById(storagePool.getId()); + if (poolVO == null) { + throw new CloudRuntimeException("updateStoragePool: storage pool not found: " + storagePool.getId()); + } + String strNewCapacityBytes = details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES); + if (strNewCapacityBytes == null) { + logger.debug("updateStoragePool: no capacityBytes change requested, skipping ONTAP resize"); + return; + } + long newCapacityBytes = Long.parseLong(strNewCapacityBytes); + + Map poolDetails = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId()); + StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(poolDetails); + logger.info("updateStoragePool: resizing ONTAP FlexVolume for pool '{}'", storagePool.getId()); + Volume volume = new Volume(); + volume.setUuid(poolDetails.get(OntapStorageConstants.VOLUME_UUID)); + volume.setName(poolDetails.get(OntapStorageConstants.VOLUME_NAME)); + try { + if (volume.getUuid() == null || volume.getUuid().isEmpty() || volume.getName() == null || volume.getName().isEmpty()) { + logger.error("updateStoragePool: Volume UUID/Name not found in details for pool: {}, cannot resize", storagePool.getName()); + throw new CloudRuntimeException("Volume UUID/Name not found in details, cannot resize ONTAP FlexVolume"); + } + storageStrategy.updateStorageVolume(volume, newCapacityBytes); + logger.info("updateStoragePool: Successfully resized ONTAP FlexVolume '{}' (UUID: {}) for pool '{}'", + volume.getName(), volume.getUuid(), storagePool.getName()); + } catch (Exception e) { + logger.error("updateStoragePool: Exception while resizing FlexVolume for pool: {}. Error: {}", + storagePool.getName(), e.getMessage(), e); + throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume for pool: " + storagePool.getName() + ". " + e.getMessage(), e); + } } @Override diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index 0ef295418118..0e7dc9aebd5b 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -367,11 +367,26 @@ public Volume createStorageVolume(String volumeName, Long size) { * @param volume the volume to update * @return the updated Volume object */ - public Volume updateStorageVolume(Volume volume) { - return null; + public Volume updateStorageVolume(Volume volume, Long newSizeBytes) { + logger.info("Resizing ONTAP volume by name: " + volume.getName() + " and uuid: " + volume.getUuid()); + String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + Volume resizeRequest = new Volume(); + resizeRequest.setSize(newSizeBytes); + try { + JobResponse jobResponse = volumeFeignClient.updateVolumeRebalancing(authHeader, volume.getUuid(), resizeRequest); + Boolean jobSucceeded = jobPollForSuccess(jobResponse.getJob().getUuid(), 10, 1000); + if (!jobSucceeded) { + logger.error("resizeStorageVolume: resize job failed for FlexVolume: " + volume.getName()); + throw new CloudRuntimeException("resizeStorageVolume: resize job failed for FlexVolume: " + volume.getName()); + } + } catch (FeignException e) { + logger.error("Exception while resizing FlexVolume: " + volume.getName(), e); + throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume: " + e.getMessage(), e); + } + logger.info("resizeStorageVolume: FlexVolume {} resized successfully to {} bytes", volume.getName(), newSizeBytes); + return volume; } - - /** + /** * Delete ONTAP Flex-Volume * Eligible only for Unified ONTAP storage * throw exception in case of disaggregated ONTAP storage diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index b25da50d4d92..475bbd257e54 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1289,7 +1289,6 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I StoragePoolVO storagePool = _storagePoolDao.findById(id); DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(storagePool.getStorageProviderName()); DataStoreLifeCycle dataStoreLifeCycle = dataStoreProvider.getDataStoreLifeCycle(); - if (dataStoreLifeCycle instanceof PrimaryDataStoreLifeCycle) { if (updatedCapacityBytes != null) { details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, updatedCapacityBytes != null ? String.valueOf(updatedCapacityBytes) : null); @@ -1302,6 +1301,7 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I if (cmd.getUrl() != null) { details.put("url", cmd.getUrl()); } + ((PrimaryDataStoreLifeCycle)dataStoreLifeCycle).updateStoragePool(pool, details); _storagePoolDao.update(id, storagePool); _storagePoolDao.updateDetails(id, details); } From 0ab1f6838324985bda3ecff6313cebe83bafbdad Mon Sep 17 00:00:00 2001 From: sr73318 Date: Sat, 8 Aug 2026 11:23:07 +0530 Subject: [PATCH 2/6] CSTACKEX-234: resolving comments --- .../OntapPrimaryDatastoreLifecycle.java | 22 +++++-------------- .../storage/service/StorageStrategy.java | 7 +++--- .../com/cloud/storage/StorageManagerImpl.java | 13 +++++------ 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 59852aa9bca9..5ed07b424695 100755 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -526,34 +526,24 @@ public boolean migrateToObjectStore(DataStore store) { @Override public void updateStoragePool(StoragePool storagePool, Map details) { - StoragePoolVO poolVO = storagePoolDao.findById(storagePool.getId()); - if (poolVO == null) { - throw new CloudRuntimeException("updateStoragePool: storage pool not found: " + storagePool.getId()); - } - String strNewCapacityBytes = details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES); - if (strNewCapacityBytes == null) { - logger.debug("updateStoragePool: no capacityBytes change requested, skipping ONTAP resize"); - return; - } - long newCapacityBytes = Long.parseLong(strNewCapacityBytes); - + long currentCapacityBytes = storagePool.getCapacityBytes(); + long newCapacityBytes = Long.parseLong(details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES)); Map poolDetails = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId()); StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(poolDetails); - logger.info("updateStoragePool: resizing ONTAP FlexVolume for pool '{}'", storagePool.getId()); Volume volume = new Volume(); volume.setUuid(poolDetails.get(OntapStorageConstants.VOLUME_UUID)); volume.setName(poolDetails.get(OntapStorageConstants.VOLUME_NAME)); try { if (volume.getUuid() == null || volume.getUuid().isEmpty() || volume.getName() == null || volume.getName().isEmpty()) { - logger.error("updateStoragePool: Volume UUID/Name not found in details for pool: {}, cannot resize", storagePool.getName()); + logger.error("Volume UUID/Name not found in details for pool: {}, cannot resize", storagePool.getName()); throw new CloudRuntimeException("Volume UUID/Name not found in details, cannot resize ONTAP FlexVolume"); } storageStrategy.updateStorageVolume(volume, newCapacityBytes); - logger.info("updateStoragePool: Successfully resized ONTAP FlexVolume '{}' (UUID: {}) for pool '{}'", - volume.getName(), volume.getUuid(), storagePool.getName()); + logger.info("Successfully resized ONTAP FlexVolume '{}' (UUID: {}) for pool '{}' from {} bytes to {} bytes", + volume.getName(), volume.getUuid(), storagePool.getName(), currentCapacityBytes, newCapacityBytes); } catch (Exception e) { - logger.error("updateStoragePool: Exception while resizing FlexVolume for pool: {}. Error: {}", + logger.error(" Exception while resizing FlexVolume for pool: {}. Error: {}", storagePool.getName(), e.getMessage(), e); throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume for pool: " + storagePool.getName() + ". " + e.getMessage(), e); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index 0e7dc9aebd5b..cbb5c43d3779 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -368,7 +368,6 @@ public Volume createStorageVolume(String volumeName, Long size) { * @return the updated Volume object */ public Volume updateStorageVolume(Volume volume, Long newSizeBytes) { - logger.info("Resizing ONTAP volume by name: " + volume.getName() + " and uuid: " + volume.getUuid()); String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword()); Volume resizeRequest = new Volume(); resizeRequest.setSize(newSizeBytes); @@ -376,14 +375,14 @@ public Volume updateStorageVolume(Volume volume, Long newSizeBytes) { JobResponse jobResponse = volumeFeignClient.updateVolumeRebalancing(authHeader, volume.getUuid(), resizeRequest); Boolean jobSucceeded = jobPollForSuccess(jobResponse.getJob().getUuid(), 10, 1000); if (!jobSucceeded) { - logger.error("resizeStorageVolume: resize job failed for FlexVolume: " + volume.getName()); - throw new CloudRuntimeException("resizeStorageVolume: resize job failed for FlexVolume: " + volume.getName()); + logger.error("resize job failed for FlexVolume: " + volume.getName()); + throw new CloudRuntimeException("resize job failed for FlexVolume: " + volume.getName()); } + logger.info("Volume is resized successfully for : " + volume.getName()); } catch (FeignException e) { logger.error("Exception while resizing FlexVolume: " + volume.getName(), e); throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume: " + e.getMessage(), e); } - logger.info("resizeStorageVolume: FlexVolume {} resized successfully to {} bytes", volume.getName(), newSizeBytes); return volume; } /** diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 475bbd257e54..d4a0c0fe83f2 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1286,23 +1286,22 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I } if (changes) { - StoragePoolVO storagePool = _storagePoolDao.findById(id); - DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(storagePool.getStorageProviderName()); + DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); DataStoreLifeCycle dataStoreLifeCycle = dataStoreProvider.getDataStoreLifeCycle(); if (dataStoreLifeCycle instanceof PrimaryDataStoreLifeCycle) { if (updatedCapacityBytes != null) { - details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, updatedCapacityBytes != null ? String.valueOf(updatedCapacityBytes) : null); - _storagePoolDao.updateCapacityBytes(id, updatedCapacityBytes); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(updatedCapacityBytes)); + pool.setCapacityBytes(updatedCapacityBytes); } if (updatedCapacityIops != null) { - details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, updatedCapacityIops != null ? String.valueOf(updatedCapacityIops) : null); - _storagePoolDao.updateCapacityIops(id, updatedCapacityIops); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, String.valueOf(updatedCapacityIops)); + pool.setCapacityIops(updatedCapacityIops); } if (cmd.getUrl() != null) { details.put("url", cmd.getUrl()); } ((PrimaryDataStoreLifeCycle)dataStoreLifeCycle).updateStoragePool(pool, details); - _storagePoolDao.update(id, storagePool); + _storagePoolDao.update(id, pool); _storagePoolDao.updateDetails(id, details); } } From b1f78c1f55dba9986af5e672b8b540c10326d23e Mon Sep 17 00:00:00 2001 From: sr73318 Date: Mon, 10 Aug 2026 10:33:03 +0530 Subject: [PATCH 3/6] CSTACKEX-234: using details parameter instead of fetching them from storage pool again in lifecycle --- .../storage/lifecycle/OntapPrimaryDatastoreLifecycle.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 5ed07b424695..bd238ee70515 100755 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -528,12 +528,11 @@ public boolean migrateToObjectStore(DataStore store) { public void updateStoragePool(StoragePool storagePool, Map details) { long currentCapacityBytes = storagePool.getCapacityBytes(); long newCapacityBytes = Long.parseLong(details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES)); - Map poolDetails = storagePoolDetailsDao.listDetailsKeyPairs(storagePool.getId()); - StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(poolDetails); + StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details); Volume volume = new Volume(); - volume.setUuid(poolDetails.get(OntapStorageConstants.VOLUME_UUID)); - volume.setName(poolDetails.get(OntapStorageConstants.VOLUME_NAME)); + volume.setUuid(details.get(OntapStorageConstants.VOLUME_UUID)); + volume.setName(details.get(OntapStorageConstants.VOLUME_NAME)); try { if (volume.getUuid() == null || volume.getUuid().isEmpty() || volume.getName() == null || volume.getName().isEmpty()) { logger.error("Volume UUID/Name not found in details for pool: {}, cannot resize", storagePool.getName()); From 67913b02e84caecc2aba5117262e324ef94f56ca Mon Sep 17 00:00:00 2001 From: sr73318 Date: Mon, 10 Aug 2026 23:24:55 +0530 Subject: [PATCH 4/6] CSTACKEX-234: removing the storagemanagerimpl changes --- .../com/cloud/storage/StorageManagerImpl.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index d4a0c0fe83f2..b25da50d4d92 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -1286,22 +1286,23 @@ public PrimaryDataStoreInfo updateStoragePool(UpdateStoragePoolCmd cmd) throws I } if (changes) { - DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); + StoragePoolVO storagePool = _storagePoolDao.findById(id); + DataStoreProvider dataStoreProvider = _dataStoreProviderMgr.getDataStoreProvider(storagePool.getStorageProviderName()); DataStoreLifeCycle dataStoreLifeCycle = dataStoreProvider.getDataStoreLifeCycle(); + if (dataStoreLifeCycle instanceof PrimaryDataStoreLifeCycle) { if (updatedCapacityBytes != null) { - details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(updatedCapacityBytes)); - pool.setCapacityBytes(updatedCapacityBytes); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, updatedCapacityBytes != null ? String.valueOf(updatedCapacityBytes) : null); + _storagePoolDao.updateCapacityBytes(id, updatedCapacityBytes); } if (updatedCapacityIops != null) { - details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, String.valueOf(updatedCapacityIops)); - pool.setCapacityIops(updatedCapacityIops); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_IOPS, updatedCapacityIops != null ? String.valueOf(updatedCapacityIops) : null); + _storagePoolDao.updateCapacityIops(id, updatedCapacityIops); } if (cmd.getUrl() != null) { details.put("url", cmd.getUrl()); } - ((PrimaryDataStoreLifeCycle)dataStoreLifeCycle).updateStoragePool(pool, details); - _storagePoolDao.update(id, pool); + _storagePoolDao.update(id, storagePool); _storagePoolDao.updateDetails(id, details); } } From d68200e1cfeb59472f38b991a6a412091a64386e Mon Sep 17 00:00:00 2001 From: sr73318 Date: Thu, 13 Aug 2026 08:16:36 +0530 Subject: [PATCH 5/6] CSTACKEX-234: resolving comments --- .../feign/client/VolumeFeignClient.java | 2 +- .../OntapPrimaryDatastoreLifecycle.java | 26 +++++++++++------- .../storage/service/StorageStrategy.java | 27 ++++++++++--------- .../storage/service/UnifiedNASStrategy.java | 2 +- .../service/UnifiedNASStrategyTest.java | 8 +++--- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java index 6384566487d4..8427f5ba7f67 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java @@ -52,5 +52,5 @@ public interface VolumeFeignClient { @RequestLine("PATCH /api/storage/volumes/{uuid}") @Headers({ "Authorization: {authHeader}"}) - JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest); + JobResponse updateVolume(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index bd238ee70515..d20cdf1ee28a 100755 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -526,24 +526,32 @@ public boolean migrateToObjectStore(DataStore store) { @Override public void updateStoragePool(StoragePool storagePool, Map details) { + String newCapacityStr = details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES); + if (newCapacityStr == null) { + logger.debug("No capacity change requested for pool: {}, skipping FlexVolume resize", storagePool.getName()); + return; + } + long currentCapacityBytes = storagePool.getCapacityBytes(); - long newCapacityBytes = Long.parseLong(details.get(PrimaryDataStoreLifeCycle.CAPACITY_BYTES)); + long newCapacityBytes = Long.parseLong(newCapacityStr); StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details); + String volumeUuid = details.get(OntapStorageConstants.VOLUME_UUID); + if (volumeUuid == null || volumeUuid.isEmpty()) { + logger.error("Volume UUID or name not found in details for pool: {}, cannot resize", storagePool.getName()); + throw new CloudRuntimeException("Volume UUID or name not found in details, cannot resize ONTAP FlexVolume"); + } + Volume volume = new Volume(); - volume.setUuid(details.get(OntapStorageConstants.VOLUME_UUID)); + volume.setUuid(volumeUuid); volume.setName(details.get(OntapStorageConstants.VOLUME_NAME)); + volume.setSize(newCapacityBytes); try { - if (volume.getUuid() == null || volume.getUuid().isEmpty() || volume.getName() == null || volume.getName().isEmpty()) { - logger.error("Volume UUID/Name not found in details for pool: {}, cannot resize", storagePool.getName()); - throw new CloudRuntimeException("Volume UUID/Name not found in details, cannot resize ONTAP FlexVolume"); - } - storageStrategy.updateStorageVolume(volume, newCapacityBytes); + storageStrategy.updateStorageVolume(volume); logger.info("Successfully resized ONTAP FlexVolume '{}' (UUID: {}) for pool '{}' from {} bytes to {} bytes", volume.getName(), volume.getUuid(), storagePool.getName(), currentCapacityBytes, newCapacityBytes); } catch (Exception e) { - logger.error(" Exception while resizing FlexVolume for pool: {}. Error: {}", - storagePool.getName(), e.getMessage(), e); + logger.error("Exception while resizing FlexVolume for pool: {}. Error: {}", storagePool.getName(), e.getMessage(), e); throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume for pool: " + storagePool.getName() + ". " + e.getMessage(), e); } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index cbb5c43d3779..9af89945f983 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -367,25 +367,28 @@ public Volume createStorageVolume(String volumeName, Long size) { * @param volume the volume to update * @return the updated Volume object */ - public Volume updateStorageVolume(Volume volume, Long newSizeBytes) { + public Volume updateStorageVolume(Volume volume) { + logger.info("Resizing ONTAP FlexVolume '{}' (UUID: {}) to {} bytes", volume.getName(), volume.getUuid(), volume.getSize()); String authHeader = OntapStorageUtils.generateAuthHeader(storage.getUsername(), storage.getPassword()); - Volume resizeRequest = new Volume(); - resizeRequest.setSize(newSizeBytes); try { - JobResponse jobResponse = volumeFeignClient.updateVolumeRebalancing(authHeader, volume.getUuid(), resizeRequest); - Boolean jobSucceeded = jobPollForSuccess(jobResponse.getJob().getUuid(), 10, 1000); - if (!jobSucceeded) { - logger.error("resize job failed for FlexVolume: " + volume.getName()); - throw new CloudRuntimeException("resize job failed for FlexVolume: " + volume.getName()); - } - logger.info("Volume is resized successfully for : " + volume.getName()); + Volume resizeRequest = new Volume(); + resizeRequest.setSize(volume.getSize()); + JobResponse jobResponse = volumeFeignClient.updateVolume(authHeader, volume.getUuid(), resizeRequest); + pollJobIfPresent(jobResponse, "resize FlexVolume [" + volume.getUuid() + "]", 10, 1000); + logger.info("FlexVolume '{}' (UUID: {}) resized successfully to {} bytes", volume.getName(), volume.getUuid(), volume.getSize()); } catch (FeignException e) { - logger.error("Exception while resizing FlexVolume: " + volume.getName(), e); + if (OntapStorageUtils.isOntapObjectNotFoundError(e)) { + String msg = String.format("Cannot resize FlexVolume '%s' (UUID: %s): volume not found on ONTAP (404). ", volume.getName(), volume.getUuid()); + logger.error(msg); + throw new CloudRuntimeException(msg, e); + } + logger.error("Exception while resizing FlexVolume '{}' (UUID: {}): {}", volume.getName(), volume.getUuid(), e.getMessage(), e); throw new CloudRuntimeException("Failed to resize ONTAP FlexVolume: " + e.getMessage(), e); } return volume; } - /** + + /** * Delete ONTAP Flex-Volume * Eligible only for Unified ONTAP storage * throw exception in case of disaggregated ONTAP storage diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java index 131d15bc6a38..0f55a742952d 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -391,7 +391,7 @@ private void assignExportPolicyToVolume(String volumeUuid, String policyName) { volumeUpdate.setNas(nas); try { - JobResponse jobResponse = volumeFeignClient.updateVolumeRebalancing(authHeader, volumeUuid, volumeUpdate); + JobResponse jobResponse = volumeFeignClient.updateVolume(authHeader, volumeUuid, volumeUpdate); if (jobResponse == null || jobResponse.getJob() == null) { throw new CloudRuntimeException("Failed to attach policy " + policyName + "to volume " + volumeUuid); } diff --git a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java index f0eb5f0ccced..60d20df8aae6 100755 --- a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java +++ b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/UnifiedNASStrategyTest.java @@ -296,7 +296,7 @@ public void testCreateAccessGroup_Success() throws Exception { when(accessGroup.getHostsToConnect()).thenReturn(hosts); doNothing().when(nasFeignClient).createExportPolicy(anyString(), any(ExportPolicy.class)); when(nasFeignClient.getExportPolicyResponse(anyString(), anyMap())).thenReturn(policyResponse); - when(volumeFeignClient.updateVolumeRebalancing(anyString(), anyString(), any())).thenReturn(jobResponse); + when(volumeFeignClient.updateVolume(anyString(), anyString(), any())).thenReturn(jobResponse); when(jobFeignClient.getJobByUUID(anyString(), anyString())).thenReturn(job); doNothing().when(storagePoolDetailsDao).addDetail(anyLong(), anyString(), anyString(), eq(true)); @@ -307,7 +307,7 @@ public void testCreateAccessGroup_Success() throws Exception { assertNotNull(result); verify(nasFeignClient).createExportPolicy(anyString(), any(ExportPolicy.class)); verify(nasFeignClient).getExportPolicyResponse(anyString(), anyMap()); - verify(volumeFeignClient).updateVolumeRebalancing(anyString(), eq("vol-uuid-123"), any()); + verify(volumeFeignClient).updateVolume(anyString(), eq("vol-uuid-123"), any()); verify(storagePoolDetailsDao, times(2)).addDetail(anyLong(), anyString(), anyString(), eq(true)); } @@ -402,7 +402,7 @@ public void testCreateAccessGroup_JobFailure() throws Exception { when(accessGroup.getHostsToConnect()).thenReturn(hosts); doNothing().when(nasFeignClient).createExportPolicy(anyString(), any(ExportPolicy.class)); when(nasFeignClient.getExportPolicyResponse(anyString(), anyMap())).thenReturn(policyResponse); - when(volumeFeignClient.updateVolumeRebalancing(anyString(), anyString(), any())).thenReturn(jobResponse); + when(volumeFeignClient.updateVolume(anyString(), anyString(), any())).thenReturn(jobResponse); when(jobFeignClient.getJobByUUID(anyString(), anyString())).thenReturn(job); assertThrows(CloudRuntimeException.class, () -> { @@ -446,7 +446,7 @@ public void testCreateAccessGroup_HostWithPrivateIP() throws Exception { when(accessGroup.getHostsToConnect()).thenReturn(hosts); doNothing().when(nasFeignClient).createExportPolicy(anyString(), any(ExportPolicy.class)); when(nasFeignClient.getExportPolicyResponse(anyString(), anyMap())).thenReturn(policyResponse); - when(volumeFeignClient.updateVolumeRebalancing(anyString(), anyString(), any())).thenReturn(jobResponse); + when(volumeFeignClient.updateVolume(anyString(), anyString(), any())).thenReturn(jobResponse); when(jobFeignClient.getJobByUUID(anyString(), anyString())).thenReturn(job); doNothing().when(storagePoolDetailsDao).addDetail(anyLong(), anyString(), anyString(), eq(true)); From 539f3693f46407046560f7abeb1dca2e5ba42afd Mon Sep 17 00:00:00 2001 From: sr73318 Date: Fri, 14 Aug 2026 16:15:03 +0530 Subject: [PATCH 6/6] CSTACKEX-234: UT's for storagepool resize --- .../OntapPrimaryDatastoreLifecycleTest.java | 104 ++++++++++++++++++ .../storage/service/StorageStrategyTest.java | 101 +++++++++++++++++ 2 files changed, 205 insertions(+) diff --git a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycleTest.java b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycleTest.java index 751b864ecfcc..ceb6b50c62d1 100644 --- a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycleTest.java +++ b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycleTest.java @@ -54,10 +54,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; import static org.mockito.Mockito.withSettings; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import java.util.HashMap; +import com.cloud.storage.StoragePool; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle; import org.apache.cloudstack.storage.provider.StorageProviderFactory; import org.apache.cloudstack.storage.service.StorageStrategy; import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; @@ -854,4 +858,104 @@ public void testAttachZone_kvmHypervisorSetsAndUpdatesPool() throws Exception { } } + // ========== updateStoragePool() Tests ========== + + @Test + public void testUpdateStoragePool_positive_resizesFlexVolume() { + // Setup + StoragePool storagePool = mock(StoragePool.class); + when(storagePool.getName()).thenReturn("test-pool"); + when(storagePool.getCapacityBytes()).thenReturn(2147483648L); // 2 GB current + + Map details = new HashMap<>(); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(5368709120L)); // 5 GB new + details.put(OntapStorageConstants.VOLUME_UUID, "flex-vol-uuid-123"); + details.put(OntapStorageConstants.VOLUME_NAME, "flexvol-name"); + details.put("protocol", "NFS3"); + + Volume updatedVolume = new Volume(); + updatedVolume.setUuid("flex-vol-uuid-123"); + updatedVolume.setSize(5368709120L); + when(storageStrategy.updateStorageVolume(any(Volume.class))).thenReturn(updatedVolume); + + try (MockedStatic utilityMock = Mockito.mockStatic(OntapStorageUtils.class)) { + utilityMock.when(() -> OntapStorageUtils.getStrategyByStoragePoolDetails(any())) + .thenReturn(storageStrategy); + + // Execute + ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details); + + // Verify + verify(storageStrategy, times(1)).updateStorageVolume(any(Volume.class)); + } + } + + @Test + public void testUpdateStoragePool_noCapacityBytesInDetails_skipsResize() { + // Setup + StoragePool storagePool = mock(StoragePool.class); + when(storagePool.getName()).thenReturn("test-pool"); + + Map details = new HashMap<>(); + details.put(OntapStorageConstants.VOLUME_UUID, "flex-vol-uuid-123"); + details.put("protocol", "NFS3"); + // No CAPACITY_BYTES key — resize should be skipped + + // Execute + ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details); + + // Verify — storageStrategy should never be called + verify(storageStrategy, never()).updateStorageVolume(any()); + } + + @Test + public void testUpdateStoragePool_missingVolumeUuid_throwsCloudRuntimeException() { + // Setup + StoragePool storagePool = mock(StoragePool.class); + when(storagePool.getName()).thenReturn("test-pool"); + when(storagePool.getCapacityBytes()).thenReturn(1073741824L); + + Map details = new HashMap<>(); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(3221225472L)); + details.put("protocol", "NFS3"); + // No VOLUME_UUID — cannot resize without it + + try (MockedStatic utilityMock = Mockito.mockStatic(OntapStorageUtils.class)) { + utilityMock.when(() -> OntapStorageUtils.getStrategyByStoragePoolDetails(any())) + .thenReturn(storageStrategy); + + // Execute & Verify + assertThrows(CloudRuntimeException.class, + () -> ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details)); + verify(storageStrategy, never()).updateStorageVolume(any()); + } + } + + @Test + public void testUpdateStoragePool_updateStorageVolumeThrows_propagatesCloudRuntimeException() { + // Setup + StoragePool storagePool = mock(StoragePool.class); + when(storagePool.getName()).thenReturn("test-pool"); + when(storagePool.getCapacityBytes()).thenReturn(1073741824L); + + Map details = new HashMap<>(); + details.put(PrimaryDataStoreLifeCycle.CAPACITY_BYTES, String.valueOf(3221225472L)); + details.put(OntapStorageConstants.VOLUME_UUID, "flex-vol-uuid-err"); + details.put(OntapStorageConstants.VOLUME_NAME, "flexvol-err"); + details.put("protocol", "NFS3"); + + when(storageStrategy.updateStorageVolume(any(Volume.class))) + .thenThrow(new CloudRuntimeException("ONTAP resize failed")); + + try (MockedStatic utilityMock = Mockito.mockStatic(OntapStorageUtils.class)) { + utilityMock.when(() -> OntapStorageUtils.getStrategyByStoragePoolDetails(any())) + .thenReturn(storageStrategy); + + // Execute & Verify + assertThrows(CloudRuntimeException.class, + () -> ontapPrimaryDatastoreLifecycle.updateStoragePool(storagePool, details)); + verify(storageStrategy, times(1)).updateStorageVolume(any(Volume.class)); + } + } + } diff --git a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/StorageStrategyTest.java b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/StorageStrategyTest.java index 070c352a7620..4e6dad4e5421 100644 --- a/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/StorageStrategyTest.java +++ b/plugins/storage/volume/ontap/src/test/java/org/apache/cloudstack/storage/service/StorageStrategyTest.java @@ -982,4 +982,105 @@ void testDeleteFlexVolSnapshotForCloudStackVolume_Feign404_TreatedAsSuccess() { verify(snapshotFeignClient).deleteSnapshot(anyString(), eq("fv-uuid-1"), eq("snap-uuid-1")); verify(jobFeignClient, never()).getJobByUUID(anyString(), anyString()); } + + // ========== updateStorageVolume() Tests ========== + + @Test + public void testUpdateStorageVolume_positive() { + // Setup + Volume volume = new Volume(); + volume.setUuid("vol-uuid-resize"); + volume.setName("flexvol-resize"); + volume.setSize(5368709120L); // 5 GB + + Job job = new Job(); + job.setUuid("resize-job-uuid"); + JobResponse jobResponse = new JobResponse(); + jobResponse.setJob(job); + + when(volumeFeignClient.updateVolume(anyString(), eq("vol-uuid-resize"), any())) + .thenReturn(jobResponse); + + Job completedJob = new Job(); + completedJob.setUuid("resize-job-uuid"); + completedJob.setState(OntapStorageConstants.JOB_SUCCESS); + when(jobFeignClient.getJobByUUID(anyString(), eq("resize-job-uuid"))) + .thenReturn(completedJob); + + // Execute + Volume result = storageStrategy.updateStorageVolume(volume); + + // Verify + assertNotNull(result); + assertEquals(5368709120L, result.getSize()); + verify(volumeFeignClient, times(1)).updateVolume(anyString(), eq("vol-uuid-resize"), any()); + verify(jobFeignClient, atLeastOnce()).getJobByUUID(anyString(), eq("resize-job-uuid")); + } + + @Test + public void testUpdateStorageVolume_jobFailed() { + // Setup + Volume volume = new Volume(); + volume.setUuid("vol-uuid-resize"); + volume.setName("flexvol-resize"); + volume.setSize(5368709120L); + + Job job = new Job(); + job.setUuid("resize-job-uuid"); + JobResponse jobResponse = new JobResponse(); + jobResponse.setJob(job); + + when(volumeFeignClient.updateVolume(anyString(), eq("vol-uuid-resize"), any())) + .thenReturn(jobResponse); + + Job failedJob = new Job(); + failedJob.setUuid("resize-job-uuid"); + failedJob.setState(OntapStorageConstants.JOB_FAILURE); + failedJob.setMessage("Resize failed"); + when(jobFeignClient.getJobByUUID(anyString(), eq("resize-job-uuid"))) + .thenReturn(failedJob); + + // Execute & Verify + Exception ex = assertThrows(CloudRuntimeException.class, + () -> storageStrategy.updateStorageVolume(volume)); + assertTrue(ex.getMessage().contains("Job failed")); + } + + @Test + public void testUpdateStorageVolume_feignException() { + // Setup + Volume volume = new Volume(); + volume.setUuid("vol-uuid-fail"); + volume.setName("flexvol-fail"); + volume.setSize(3221225472L); + + FeignException feignException = mock(FeignException.class); + when(feignException.status()).thenReturn(500); + when(volumeFeignClient.updateVolume(anyString(), eq("vol-uuid-fail"), any())) + .thenThrow(feignException); + + // Execute & Verify + Exception ex = assertThrows(CloudRuntimeException.class, + () -> storageStrategy.updateStorageVolume(volume)); + assertTrue(ex.getMessage().contains("Failed to resize ONTAP FlexVolume")); + } + + @Test + public void testUpdateStorageVolume_notFound_404_throwsCloudRuntimeException() { + // Setup + Volume volume = new Volume(); + volume.setUuid("vol-uuid-notfound"); + volume.setName("flexvol-notfound"); + volume.setSize(1073741824L); + + FeignException feignEx = mock(FeignException.class); + when(feignEx.status()).thenReturn(404); + when(volumeFeignClient.updateVolume(anyString(), eq("vol-uuid-notfound"), any())) + .thenThrow(feignEx); + + // Execute & Verify — 404 means volume not found on ONTAP, should throw + CloudRuntimeException ex = assertThrows(CloudRuntimeException.class, + () -> storageStrategy.updateStorageVolume(volume)); + assertTrue(ex.getMessage().contains("not found on ONTAP")); + } }