This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch 4.19
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.19 by this push:
     new 88ce639255d Linstor:  implement volume and storage stats (#10850)
88ce639255d is described below

commit 88ce639255dac458c12d8f56547c63d259265f08
Author: Rene Peinthor <[email protected]>
AuthorDate: Tue May 13 10:06:35 2025 +0200

    Linstor:  implement volume and storage stats (#10850)
---
 plugins/storage/volume/linstor/CHANGELOG.md        |  5 ++
 .../driver/LinstorPrimaryDataStoreDriverImpl.java  | 78 +++++++++++++++++++---
 .../util/LinstorConfigurationManager.java          |  9 ++-
 .../storage/datastore/util/LinstorUtil.java        | 32 ++++++++-
 pom.xml                                            |  2 +-
 5 files changed, 112 insertions(+), 14 deletions(-)

diff --git a/plugins/storage/volume/linstor/CHANGELOG.md 
b/plugins/storage/volume/linstor/CHANGELOG.md
index 7e9d754b9f6..2abda3ebc50 100644
--- a/plugins/storage/volume/linstor/CHANGELOG.md
+++ b/plugins/storage/volume/linstor/CHANGELOG.md
@@ -5,6 +5,11 @@ 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-05-07]
+
+### Added
+- Implemented storage/volume stats
+
 ## [2025-03-13]
 
 ### 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 a0cb5d17444..3b384831518 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
@@ -28,11 +28,11 @@ import com.linbit.linstor.api.model.ResourceDefinition;
 import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
 import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
 import com.linbit.linstor.api.model.ResourceDefinitionCreate;
-
 import com.linbit.linstor.api.model.ResourceDefinitionModify;
 import com.linbit.linstor.api.model.ResourceGroup;
 import com.linbit.linstor.api.model.ResourceGroupSpawn;
 import com.linbit.linstor.api.model.ResourceMakeAvailable;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
 import com.linbit.linstor.api.model.Snapshot;
 import com.linbit.linstor.api.model.SnapshotRestore;
 import com.linbit.linstor.api.model.VolumeDefinition;
@@ -132,6 +132,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     @Inject
     private HostDao _hostDao;
 
+    private long volumeStatsLastUpdate = 0L;
+    private final Map<String, Pair<Long, Long>> volumeStats = new HashMap<>();
+
     public LinstorPrimaryDataStoreDriverImpl()
     {
     }
@@ -401,9 +404,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
         }
     }
 
-    private String getRscGrp(StoragePoolVO storagePoolVO) {
-        return storagePoolVO.getUserInfo() != null && 
!storagePoolVO.getUserInfo().isEmpty() ?
-            storagePoolVO.getUserInfo() : "DfltRscGrp";
+    private String getRscGrp(StoragePool storagePool) {
+        return storagePool.getUserInfo() != null && 
!storagePool.getUserInfo().isEmpty() ?
+            storagePool.getUserInfo() : "DfltRscGrp";
     }
 
     /**
@@ -616,7 +619,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
      */
     private void updateRscGrpIfNecessary(DevelopersApi api, String rscName, 
String tgtRscGrp) throws ApiException {
         List<ResourceDefinition> rscDfns = api.resourceDefinitionList(
-                Collections.singletonList(rscName), null, null, null);
+                Collections.singletonList(rscName), false, null, null, null);
         if (rscDfns != null && !rscDfns.isEmpty()) {
             ResourceDefinition rscDfn = rscDfns.get(0);
 
@@ -646,7 +649,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     private void deleteTemplateForProps(
             DevelopersApi api, String rscName) throws ApiException {
         List<ResourceDefinition> rdList = api.resourceDefinitionList(
-                Collections.singletonList(rscName), null, null, null);
+                Collections.singletonList(rscName), false, null, null, null);
 
         if (CollectionUtils.isNotEmpty(rdList)) {
             ResourceDefinitionModify rdm = new ResourceDefinitionModify();
@@ -1504,22 +1507,77 @@ public class LinstorPrimaryDataStoreDriverImpl 
implements PrimaryDataStoreDriver
 
     @Override
     public boolean canProvideStorageStats() {
-        return false;
+        return true;
     }
 
     @Override
     public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
-        return null;
+        s_logger.debug(String.format("Requesting storage stats: %s", 
storagePool));
+        return LinstorUtil.getStorageStats(storagePool.getHostAddress(), 
getRscGrp(storagePool));
     }
 
     @Override
     public boolean canProvideVolumeStats() {
-        return false;
+        return LinstorConfigurationManager.VolumeStatsCacheTime.value() > 0;
+    }
+
+    /**
+     * Updates the cache map containing current allocated size data.
+     * @param api Linstor Developers api object
+     */
+    private void fillVolumeStatsCache(DevelopersApi api) {
+        try {
+            s_logger.trace("Start volume stats cache update");
+            List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.emptyList(),
+                    Collections.emptyList(),
+                    Collections.emptyList(),
+                    null,
+                    null,
+                    null);
+
+            List<ResourceDefinition> rscDfns = api.resourceDefinitionList(
+                    Collections.emptyList(), true, null, null, null);
+
+            HashMap<String, Long> resSizeMap = new HashMap<>();
+            for (ResourceDefinition rscDfn : rscDfns) {
+                if (CollectionUtils.isNotEmpty(rscDfn.getVolumeDefinitions())) 
{
+                    resSizeMap.put(rscDfn.getName(), 
rscDfn.getVolumeDefinitions().get(0).getSizeKib() * 1024);
+                }
+            }
+
+            HashMap<String, Long> allocSizeMap = new HashMap<>();
+            for (ResourceWithVolumes rsc : resources) {
+                if (!LinstorUtil.isRscDiskless(rsc) && 
!rsc.getVolumes().isEmpty()) {
+                    long allocatedBytes = 
allocSizeMap.getOrDefault(rsc.getName(), 0L);
+                    allocSizeMap.put(rsc.getName(), Math.max(allocatedBytes, 
rsc.getVolumes().get(0).getAllocatedSizeKib() * 1024));
+                }
+            }
+
+            volumeStats.clear();
+            for (Map.Entry<String, Long> entry : allocSizeMap.entrySet()) {
+                Long reserved = resSizeMap.getOrDefault(entry.getKey(), 0L);
+                Pair<Long, Long> volStat = new Pair<>(entry.getValue(), 
reserved);
+                volumeStats.put(entry.getKey(), volStat);
+            }
+            volumeStatsLastUpdate = System.currentTimeMillis();
+            s_logger.trace("Done volume stats cache update: " + 
volumeStats.size());
+        } catch (ApiException e) {
+            s_logger.error("Unable to fetch Linstor resources: " + 
e.getBestMessage());
+        }
     }
 
     @Override
     public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String 
volumeId) {
-        return null;
+        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+        synchronized (volumeStats) {
+            long invalidateCacheTime = volumeStatsLastUpdate +
+                    LinstorConfigurationManager.VolumeStatsCacheTime.value() * 
1000;
+            if (invalidateCacheTime < System.currentTimeMillis()) {
+                fillVolumeStatsCache(api);
+            }
+            return volumeStats.get(LinstorUtil.RSC_PREFIX + volumeId);
+        }
     }
 
     @Override
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
index 90ebf30f7cd..85a0804dbab 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
@@ -24,7 +24,14 @@ public class LinstorConfigurationManager implements 
Configurable
     public static final ConfigKey<Boolean> BackupSnapshots = new 
ConfigKey<>(Boolean.class, "lin.backup.snapshots", "Advanced", "true",
         "Backup Linstor primary storage snapshots to secondary storage 
(deleting ps snapshot)", true, ConfigKey.Scope.Global, null);
 
-    public static final ConfigKey<?>[] CONFIG_KEYS = new ConfigKey<?>[] { 
BackupSnapshots };
+    public static final ConfigKey<Integer> VolumeStatsCacheTime = new 
ConfigKey<>("Advanced", Integer.class,
+            "lin.volumes.stats.cachetime", "300",
+            "Cache time of volume stats for Linstor volumes. 0 to disable 
volume stats",
+            false);
+
+    public static final ConfigKey<?>[] CONFIG_KEYS = new ConfigKey<?>[] {
+            BackupSnapshots, VolumeStatsCacheTime
+    };
 
     @Override
     public String getConfigComponentName()
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
index e252753502c..60d06590006 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
@@ -195,6 +195,30 @@ public class LinstorUtil {
         }
     }
 
+    public static Pair<Long, Long> getStorageStats(String linstorUrl, String 
rscGroupName) {
+        DevelopersApi linstorApi = getLinstorAPI(linstorUrl);
+        try {
+            List<StoragePool> storagePools = 
LinstorUtil.getRscGroupStoragePools(linstorApi, rscGroupName);
+
+            long capacity = storagePools.stream()
+                    .filter(sp -> sp.getProviderKind() != 
ProviderKind.DISKLESS)
+                    .mapToLong(sp -> sp.getTotalCapacity() != null ? 
sp.getTotalCapacity() : 0L)
+                    .sum() * 1024;  // linstor uses kiB
+
+            long used = storagePools.stream()
+                    .filter(sp -> sp.getProviderKind() != 
ProviderKind.DISKLESS)
+                    .mapToLong(sp -> sp.getTotalCapacity() != null && 
sp.getFreeCapacity() != null ?
+                            sp.getTotalCapacity() - sp.getFreeCapacity() : 0L)
+                    .sum() * 1024; // linstor uses Kib
+            s_logger.debug(
+                    String.format("Linstor(%s;%s): storageStats -> %d/%d", 
linstorUrl, rscGroupName, capacity, used));
+            return new Pair<>(capacity, used);
+        } catch (ApiException apiEx) {
+            s_logger.error(apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
     /**
      * Check if any resource of the given name is InUse on any host.
      *
@@ -303,7 +327,7 @@ public class LinstorUtil {
     public static List<ResourceDefinition> getRDListStartingWith(DevelopersApi 
api, String startWith)
             throws ApiException
     {
-        List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, 
null, null, null);
+        List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, 
false, null, null, null);
 
         return rscDfns.stream()
                 .filter(rscDfn -> 
rscDfn.getName().toLowerCase().startsWith(startWith.toLowerCase()))
@@ -386,7 +410,7 @@ public class LinstorUtil {
      */
     public static ResourceDefinition findResourceDefinition(DevelopersApi api, 
String rscName, String rscGrpName)
             throws ApiException {
-        List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, 
null, null, null);
+        List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, 
false, null, null, null);
 
         List<ResourceDefinition> rdsStartingWith = rscDfns.stream()
                 .filter(rscDfn -> 
rscDfn.getName().toLowerCase().startsWith(rscName.toLowerCase()))
@@ -402,4 +426,8 @@ public class LinstorUtil {
 
         return rd.orElseGet(() -> rdsStartingWith.get(0));
     }
+
+    public static boolean isRscDiskless(ResourceWithVolumes rsc) {
+        return rsc.getFlags() != null && 
rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS);
+    }
 }
diff --git a/pom.xml b/pom.xml
index 483d380cb9c..4662356f203 100644
--- a/pom.xml
+++ b/pom.xml
@@ -169,7 +169,7 @@
         <cs.nitro.version>10.1</cs.nitro.version>
         <cs.opensaml.version>2.6.6</cs.opensaml.version>
         <cs.rados-java.version>0.6.0</cs.rados-java.version>
-        <cs.java-linstor.version>0.6.0</cs.java-linstor.version>
+        <cs.java-linstor.version>0.6.1</cs.java-linstor.version>
         <cs.reflections.version>0.10.2</cs.reflections.version>
         <cs.servicemix.version>3.4.4_1</cs.servicemix.version>
         <cs.servlet.version>4.0.1</cs.servlet.version>

Reply via email to