Skip to content

Commit 88ce639

Browse files
authored
Linstor: implement volume and storage stats (#10850)
1 parent 47a2682 commit 88ce639

File tree

5 files changed

+112
-14
lines changed

5 files changed

+112
-14
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: 68 additions & 10 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
/**
@@ -616,7 +619,7 @@ private void resizeResource(DevelopersApi api, String resourceName, long sizeByt
616619
*/
617620
private void updateRscGrpIfNecessary(DevelopersApi api, String rscName, String tgtRscGrp) throws ApiException {
618621
List<ResourceDefinition> rscDfns = api.resourceDefinitionList(
619-
Collections.singletonList(rscName), null, null, null);
622+
Collections.singletonList(rscName), false, null, null, null);
620623
if (rscDfns != null && !rscDfns.isEmpty()) {
621624
ResourceDefinition rscDfn = rscDfns.get(0);
622625

@@ -646,7 +649,7 @@ private void updateRscGrpIfNecessary(DevelopersApi api, String rscName, String t
646649
private void deleteTemplateForProps(
647650
DevelopersApi api, String rscName) throws ApiException {
648651
List<ResourceDefinition> rdList = api.resourceDefinitionList(
649-
Collections.singletonList(rscName), null, null, null);
652+
Collections.singletonList(rscName), false, null, null, null);
650653

651654
if (CollectionUtils.isNotEmpty(rdList)) {
652655
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
@@ -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: 30 additions & 2 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
*
@@ -303,7 +327,7 @@ public static ApiCallRcList applyAuxProps(DevelopersApi api, String rscName, Str
303327
public static List<ResourceDefinition> getRDListStartingWith(DevelopersApi api, String startWith)
304328
throws ApiException
305329
{
306-
List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, null, null, null);
330+
List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, false, null, null, null);
307331

308332
return rscDfns.stream()
309333
.filter(rscDfn -> rscDfn.getName().toLowerCase().startsWith(startWith.toLowerCase()))
@@ -386,7 +410,7 @@ public static void setAuxTemplateForProperty(DevelopersApi api, String rscName,
386410
*/
387411
public static ResourceDefinition findResourceDefinition(DevelopersApi api, String rscName, String rscGrpName)
388412
throws ApiException {
389-
List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, null, null, null);
413+
List<ResourceDefinition> rscDfns = api.resourceDefinitionList(null, false, null, null, null);
390414

391415
List<ResourceDefinition> rdsStartingWith = rscDfns.stream()
392416
.filter(rscDfn -> rscDfn.getName().toLowerCase().startsWith(rscName.toLowerCase()))
@@ -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
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169
<cs.nitro.version>10.1</cs.nitro.version>
170170
<cs.opensaml.version>2.6.6</cs.opensaml.version>
171171
<cs.rados-java.version>0.6.0</cs.rados-java.version>
172-
<cs.java-linstor.version>0.6.0</cs.java-linstor.version>
172+
<cs.java-linstor.version>0.6.1</cs.java-linstor.version>
173173
<cs.reflections.version>0.10.2</cs.reflections.version>
174174
<cs.servicemix.version>3.4.4_1</cs.servicemix.version>
175175
<cs.servlet.version>4.0.1</cs.servlet.version>

0 commit comments

Comments
 (0)