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>