diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index a96b7c75b2b..c0991a9aa2b 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2025-08-05] + +### Fixed + +- getVolumeStats wasn't correctly working if multiple Linstor clusters/primary storages are used. + ## [2025-07-01] ### Fixed diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index a0999090355..306e9259936 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -137,7 +137,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver private HostDao _hostDao; @Inject private VMTemplateDao _vmTemplateDao; - private long volumeStatsLastUpdate = 0L; + private final Map volumeStatsLastUpdate = new HashMap<>(); private final Map> volumeStats = new HashMap<>(); public LinstorPrimaryDataStoreDriverImpl() @@ -1535,11 +1535,12 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver /** * Updates the cache map containing current allocated size data. - * @param api Linstor Developers api object + * @param linstorAddr Linstor cluster api address */ - private void fillVolumeStatsCache(DevelopersApi api) { + private void fillVolumeStatsCache(String linstorAddr) { + final DevelopersApi api = LinstorUtil.getLinstorAPI(linstorAddr); try { - logger.trace("Start volume stats cache update"); + logger.trace("Start volume stats cache update for " + linstorAddr); List resources = api.viewResources( Collections.emptyList(), Collections.emptyList(), @@ -1566,14 +1567,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver } } - volumeStats.clear(); + volumeStats.keySet().removeIf(key -> key.startsWith(linstorAddr)); for (Map.Entry entry : allocSizeMap.entrySet()) { Long reserved = resSizeMap.getOrDefault(entry.getKey(), 0L); Pair volStat = new Pair<>(entry.getValue(), reserved); - volumeStats.put(entry.getKey(), volStat); + volumeStats.put(linstorAddr + "/" + entry.getKey(), volStat); } - volumeStatsLastUpdate = System.currentTimeMillis(); - logger.trace("Done volume stats cache update: {}", volumeStats.size()); + volumeStatsLastUpdate.put(linstorAddr, System.currentTimeMillis()); + logger.debug(String.format("Done volume stats cache update for %s: %d", linstorAddr, volumeStats.size())); } catch (ApiException e) { logger.error("Unable to fetch Linstor resources: {}", e.getBestMessage()); } @@ -1581,14 +1582,19 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver @Override public Pair getVolumeStats(StoragePool storagePool, String volumeId) { - final DevelopersApi api = LinstorUtil.getLinstorAPI(storagePool.getHostAddress()); + String linstorAddr = storagePool.getHostAddress(); synchronized (volumeStats) { - long invalidateCacheTime = volumeStatsLastUpdate + + long invalidateCacheTime = volumeStatsLastUpdate.getOrDefault(storagePool.getHostAddress(), 0L) + LinstorConfigurationManager.VolumeStatsCacheTime.value() * 1000; if (invalidateCacheTime < System.currentTimeMillis()) { - fillVolumeStatsCache(api); + fillVolumeStatsCache(storagePool.getHostAddress()); } - return volumeStats.get(LinstorUtil.RSC_PREFIX + volumeId); + String volumeKey = linstorAddr + "/" + LinstorUtil.RSC_PREFIX + volumeId; + Pair sizePair = volumeStats.get(volumeKey); + if (sizePair == null) { + logger.warn(String.format("Volumestats for %s not found in cache", volumeKey)); + } + return sizePair; } } diff --git a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue index 31e3fadc9f6..ea0b9c8c909 100644 --- a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue +++ b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue @@ -2147,7 +2147,11 @@ export default { resolve(result) }).catch(error => { message = error.response.headers['x-description'] - reject(message) + if (message.includes('is already in the database')) { + resolve() + } else { + reject(message) + } }) }) }, @@ -2159,11 +2163,7 @@ export default { resolve() }).catch(error => { message = error.response.headers['x-description'] - if (message.includes('is already in the database')) { - resolve() - } else { - reject(message) - } + reject(message) }) }) },