Skip to content

Commit e7ac613

Browse files
committed
linstor: implement storage and volume stats methods
Using storage stats not every agent has to query the controller for current used and capacity storage. Volume stats will show physical used size for thin volume in Cloudstack. This volume stats are cached to not query the Linstor controller each time a volume is asked for.
1 parent d2499c0 commit e7ac613

File tree

4 files changed

+107
-9
lines changed

4 files changed

+107
-9
lines changed

plugins/storage/volume/linstor/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2025-05-07]
9+
10+
### Added
11+
- Implemented storage/volume stats
12+
813
## [2025-03-13]
914

1015
### Fixed

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
2929
import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
3030
import com.linbit.linstor.api.model.ResourceDefinitionCreate;
31-
3231
import com.linbit.linstor.api.model.ResourceDefinitionModify;
3332
import com.linbit.linstor.api.model.ResourceGroup;
3433
import com.linbit.linstor.api.model.ResourceGroupSpawn;
3534
import com.linbit.linstor.api.model.ResourceMakeAvailable;
35+
import com.linbit.linstor.api.model.ResourceWithVolumes;
3636
import com.linbit.linstor.api.model.Snapshot;
3737
import com.linbit.linstor.api.model.SnapshotRestore;
3838
import com.linbit.linstor.api.model.VolumeDefinition;
@@ -132,6 +132,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
132132
@Inject
133133
private HostDao _hostDao;
134134

135+
private long volumeStatsLastUpdate = 0L;
136+
private final Map<String, Pair<Long, Long>> volumeStats = new HashMap<>();
137+
135138
public LinstorPrimaryDataStoreDriverImpl()
136139
{
137140
}
@@ -401,9 +404,9 @@ private void applyQoSSettings(StoragePoolVO storagePool, DevelopersApi api, Stri
401404
}
402405
}
403406

404-
private String getRscGrp(StoragePoolVO storagePoolVO) {
405-
return storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
406-
storagePoolVO.getUserInfo() : "DfltRscGrp";
407+
private String getRscGrp(StoragePool storagePool) {
408+
return storagePool.getUserInfo() != null && !storagePool.getUserInfo().isEmpty() ?
409+
storagePool.getUserInfo() : "DfltRscGrp";
407410
}
408411

409412
/**
@@ -1504,22 +1507,77 @@ public void takeSnapshot(SnapshotInfo snapshotInfo, AsyncCompletionCallback<Crea
15041507

15051508
@Override
15061509
public boolean canProvideStorageStats() {
1507-
return false;
1510+
return true;
15081511
}
15091512

15101513
@Override
15111514
public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
1512-
return null;
1515+
s_logger.debug(String.format("Requesting storage stats: %s", storagePool));
1516+
return LinstorUtil.getStorageStats(storagePool.getHostAddress(), getRscGrp(storagePool));
15131517
}
15141518

15151519
@Override
15161520
public boolean canProvideVolumeStats() {
1517-
return false;
1521+
return LinstorConfigurationManager.VolumeStatsCacheTime.value() > 0;
1522+
}
1523+
1524+
/**
1525+
* Updates the cache map containing current allocated size data.
1526+
* @param api Linstor Developers api object
1527+
*/
1528+
private void fillVolumeStatsCache(DevelopersApi api) {
1529+
try {
1530+
s_logger.trace("Start volume stats cache update");
1531+
List<ResourceWithVolumes> resources = api.viewResources(
1532+
Collections.emptyList(),
1533+
Collections.emptyList(),
1534+
Collections.emptyList(),
1535+
null,
1536+
null,
1537+
null);
1538+
1539+
List<ResourceDefinition> rscDfns = api.resourceDefinitionList(
1540+
Collections.emptyList(), true, null, null, null);
1541+
1542+
HashMap<String, Long> resSizeMap = new HashMap<>();
1543+
for (ResourceDefinition rscDfn : rscDfns) {
1544+
if (CollectionUtils.isNotEmpty(rscDfn.getVolumeDefinitions())) {
1545+
resSizeMap.put(rscDfn.getName(), rscDfn.getVolumeDefinitions().get(0).getSizeKib() * 1024);
1546+
}
1547+
}
1548+
1549+
HashMap<String, Long> allocSizeMap = new HashMap<>();
1550+
for (ResourceWithVolumes rsc : resources) {
1551+
if (!LinstorUtil.isRscDiskless(rsc) && !rsc.getVolumes().isEmpty()) {
1552+
long allocatedBytes = allocSizeMap.getOrDefault(rsc.getName(), 0L);
1553+
allocSizeMap.put(rsc.getName(), Math.max(allocatedBytes, rsc.getVolumes().get(0).getAllocatedSizeKib() * 1024));
1554+
}
1555+
}
1556+
1557+
volumeStats.clear();
1558+
for (Map.Entry<String, Long> entry : allocSizeMap.entrySet()) {
1559+
Long reserved = resSizeMap.getOrDefault(entry.getKey(), 0L);
1560+
Pair<Long, Long> volStat = new Pair<>(entry.getValue(), reserved);
1561+
volumeStats.put(entry.getKey(), volStat);
1562+
}
1563+
volumeStatsLastUpdate = System.currentTimeMillis();
1564+
s_logger.trace("Done volume stats cache update: " + volumeStats.size());
1565+
} catch (ApiException e) {
1566+
s_logger.error("Unable to fetch Linstor resources: " + e.getBestMessage());
1567+
}
15181568
}
15191569

15201570
@Override
15211571
public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
1522-
return null;
1572+
final DevelopersApi api = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
1573+
synchronized (volumeStats) {
1574+
long invalidateCacheTime = volumeStatsLastUpdate +
1575+
LinstorConfigurationManager.VolumeStatsCacheTime.value() * 1000;
1576+
if (invalidateCacheTime < System.currentTimeMillis()) {
1577+
fillVolumeStatsCache(api);
1578+
}
1579+
return volumeStats.get(LinstorUtil.RSC_PREFIX + volumeId);
1580+
}
15231581
}
15241582

15251583
@Override

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ public class LinstorConfigurationManager implements Configurable
2424
public static final ConfigKey<Boolean> BackupSnapshots = new ConfigKey<>(Boolean.class, "lin.backup.snapshots", "Advanced", "true",
2525
"Backup Linstor primary storage snapshots to secondary storage (deleting ps snapshot)", true, ConfigKey.Scope.Global, null);
2626

27-
public static final ConfigKey<?>[] CONFIG_KEYS = new ConfigKey<?>[] { BackupSnapshots };
27+
public static final ConfigKey<Integer> VolumeStatsCacheTime = new ConfigKey<>("Advanced", Integer.class,
28+
"lin.volumes.stats.cachetime", "300",
29+
"Cache time of volume stats for Linstor volumes. 0 to disable volume stats",
30+
false);
31+
32+
public static final ConfigKey<?>[] CONFIG_KEYS = new ConfigKey<?>[] {
33+
BackupSnapshots, VolumeStatsCacheTime
34+
};
2835

2936
@Override
3037
public String getConfigComponentName()

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,30 @@ public static long getCapacityBytes(String linstorUrl, String rscGroupName) {
195195
}
196196
}
197197

198+
public static Pair<Long, Long> getStorageStats(String linstorUrl, String rscGroupName) {
199+
DevelopersApi linstorApi = getLinstorAPI(linstorUrl);
200+
try {
201+
List<StoragePool> storagePools = LinstorUtil.getRscGroupStoragePools(linstorApi, rscGroupName);
202+
203+
long capacity = storagePools.stream()
204+
.filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS)
205+
.mapToLong(sp -> sp.getTotalCapacity() != null ? sp.getTotalCapacity() : 0L)
206+
.sum() * 1024; // linstor uses kiB
207+
208+
long used = storagePools.stream()
209+
.filter(sp -> sp.getProviderKind() != ProviderKind.DISKLESS)
210+
.mapToLong(sp -> sp.getTotalCapacity() != null && sp.getFreeCapacity() != null ?
211+
sp.getTotalCapacity() - sp.getFreeCapacity() : 0L)
212+
.sum() * 1024; // linstor uses Kib
213+
s_logger.debug(
214+
String.format("Linstor(%s;%s): storageStats -> %d/%d", linstorUrl, rscGroupName, capacity, used));
215+
return new Pair<>(capacity, used);
216+
} catch (ApiException apiEx) {
217+
s_logger.error(apiEx.getMessage());
218+
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
219+
}
220+
}
221+
198222
/**
199223
* Check if any resource of the given name is InUse on any host.
200224
*
@@ -402,4 +426,8 @@ public static ResourceDefinition findResourceDefinition(DevelopersApi api, Strin
402426

403427
return rd.orElseGet(() -> rdsStartingWith.get(0));
404428
}
429+
430+
public static boolean isRscDiskless(ResourceWithVolumes rsc) {
431+
return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS);
432+
}
405433
}

0 commit comments

Comments
 (0)