Skip to content

Commit bd488c4

Browse files
authored
server, plugin: enhance storage stats for IOPS (#10034)
Adds framework layer change to allow retrieving and storing IOPS stats for storage pools. Custom `PrimaryStoreDriver` can implement method - `getStorageIopsStats` for returning IOPS stats. Existing method `getUsedIops` can also be overridden by such plugins when only used IOPS is returned. For testing purpose, implementation has been added for simulator hypervisor plugin to return capacity and used IOPS for a pool. For local storage pool, implementation has been added using iostat to return currently used IOPS. StoragePoolResponse class has been updated to return IOPS values which allows showing IOPS values in UI for different storage pool related views and APIs. Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 9bc283e commit bd488c4

File tree

29 files changed

+788
-108
lines changed

29 files changed

+788
-108
lines changed

api/src/main/java/com/cloud/storage/StorageStats.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ public interface StorageStats {
2626
* @return bytes capacity of the storage server
2727
*/
2828
public long getCapacityBytes();
29+
30+
Long getCapacityIops();
31+
Long getUsedIops();
2932
}

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ public class ApiConstants {
509509
public static final String URL = "url";
510510
public static final String USAGE_INTERFACE = "usageinterface";
511511
public static final String USED_SUBNETS = "usedsubnets";
512+
public static final String USED_IOPS = "usediops";
512513
public static final String USER_DATA = "userdata";
513514

514515
public static final String USER_DATA_NAME = "userdataname";

api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations {
9797
@Param(description = "total min IOPS currently in use by volumes")
9898
private Long allocatedIops;
9999

100+
@SerializedName(ApiConstants.USED_IOPS)
101+
@Param(description = "total IOPS currently in use", since = "4.20.1")
102+
private Long usedIops;
103+
100104
@SerializedName(ApiConstants.STORAGE_CUSTOM_STATS)
101105
@Param(description = "the storage pool custom stats", since = "4.18.1")
102106
private Map<String, String> customStats;
@@ -312,6 +316,14 @@ public void setAllocatedIops(Long allocatedIops) {
312316
this.allocatedIops = allocatedIops;
313317
}
314318

319+
public Long getUsedIops() {
320+
return usedIops;
321+
}
322+
323+
public void setUsedIops(Long usedIops) {
324+
this.usedIops = usedIops;
325+
}
326+
315327
public Map<String, String> getCustomStats() {
316328
return customStats;
317329
}

core/src/main/java/com/cloud/agent/api/GetStorageStatsAnswer.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,46 @@ public class GetStorageStatsAnswer extends Answer implements StorageStats {
2727
protected GetStorageStatsAnswer() {
2828
}
2929

30-
protected long used;
30+
protected long usedBytes;
3131

32-
protected long capacity;
32+
protected long capacityBytes;
33+
34+
protected Long capacityIops;
35+
36+
protected Long usedIops;
3337

3438
@Override
3539
public long getByteUsed() {
36-
return used;
40+
return usedBytes;
3741
}
3842

3943
@Override
4044
public long getCapacityBytes() {
41-
return capacity;
45+
return capacityBytes;
46+
}
47+
48+
@Override
49+
public Long getCapacityIops() {
50+
return capacityIops;
51+
}
52+
53+
@Override
54+
public Long getUsedIops() {
55+
return usedIops;
56+
}
57+
58+
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long capacityBytes, long usedBytes) {
59+
super(cmd, true, null);
60+
this.capacityBytes = capacityBytes;
61+
this.usedBytes = usedBytes;
4262
}
4363

44-
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long capacity, long used) {
64+
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long capacityBytes, long usedBytes, Long capacityIops, Long usedIops) {
4565
super(cmd, true, null);
46-
this.capacity = capacity;
47-
this.used = used;
66+
this.capacityBytes = capacityBytes;
67+
this.usedBytes = usedBytes;
68+
this.capacityIops = capacityIops;
69+
this.usedIops = usedIops;
4870
}
4971

5072
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, String details) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.api;
18+
19+
import org.junit.Assert;
20+
import org.junit.Test;
21+
import org.junit.runner.RunWith;
22+
import org.mockito.junit.MockitoJUnitRunner;
23+
24+
@RunWith(MockitoJUnitRunner.class)
25+
public class GetStorageStatsAnswerTest {
26+
27+
@Test
28+
public void testDefaultConstructor() {
29+
GetStorageStatsAnswer answer = new GetStorageStatsAnswer();
30+
31+
Assert.assertEquals(0, answer.getByteUsed());
32+
Assert.assertEquals(0, answer.getCapacityBytes());
33+
Assert.assertNull(answer.getCapacityIops());
34+
Assert.assertNull(answer.getUsedIops());
35+
}
36+
37+
@Test
38+
public void testConstructorWithCapacityAndUsedBytes() {
39+
GetStorageStatsCommand mockCmd = new GetStorageStatsCommand();
40+
long capacityBytes = 1024L;
41+
long usedBytes = 512L;
42+
43+
GetStorageStatsAnswer answer = new GetStorageStatsAnswer(mockCmd, capacityBytes, usedBytes);
44+
45+
Assert.assertEquals(capacityBytes, answer.getCapacityBytes());
46+
Assert.assertEquals(usedBytes, answer.getByteUsed());
47+
Assert.assertNull(answer.getCapacityIops());
48+
Assert.assertNull(answer.getUsedIops());
49+
}
50+
51+
@Test
52+
public void testConstructorWithIops() {
53+
GetStorageStatsCommand mockCmd = new GetStorageStatsCommand();
54+
long capacityBytes = 2048L;
55+
long usedBytes = 1024L;
56+
Long capacityIops = 1000L;
57+
Long usedIops = 500L;
58+
59+
GetStorageStatsAnswer answer = new GetStorageStatsAnswer(mockCmd, capacityBytes, usedBytes, capacityIops, usedIops);
60+
61+
Assert.assertEquals(capacityBytes, answer.getCapacityBytes());
62+
Assert.assertEquals(usedBytes, answer.getByteUsed());
63+
Assert.assertEquals(capacityIops, answer.getCapacityIops());
64+
Assert.assertEquals(usedIops, answer.getUsedIops());
65+
}
66+
67+
@Test
68+
public void testErrorConstructor() {
69+
GetStorageStatsCommand mockCmd = new GetStorageStatsCommand();
70+
String errorDetails = "An error occurred";
71+
72+
GetStorageStatsAnswer answer = new GetStorageStatsAnswer(mockCmd, errorDetails);
73+
74+
Assert.assertFalse(answer.getResult());
75+
Assert.assertEquals(errorDetails, answer.getDetails());
76+
Assert.assertEquals(0, answer.getCapacityBytes());
77+
Assert.assertEquals(0, answer.getByteUsed());
78+
Assert.assertNull(answer.getCapacityIops());
79+
Assert.assertNull(answer.getUsedIops());
80+
}
81+
}

debian/control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Description: CloudStack server library
2424

2525
Package: cloudstack-agent
2626
Architecture: all
27-
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd
27+
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat
2828
Recommends: init-system-helpers
2929
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
3030
Description: CloudStack agent

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ default Map<String, String> getCustomStorageStats(StoragePool pool) {
111111
*/
112112
Pair<Long, Long> getStorageStats(StoragePool storagePool);
113113

114+
/**
115+
* Intended for managed storage
116+
* returns the capacity and used IOPS or null if not supported
117+
*/
118+
default Pair<Long, Long> getStorageIopsStats(StoragePool storagePool) {
119+
return null;
120+
}
121+
114122
/**
115123
* intended for managed storage
116124
* returns true if the storage can provide the volume stats (physical and virtual size)

engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41700to41710.java

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
2424
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
2525
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
26+
import org.apache.commons.collections.CollectionUtils;
2627

2728
import com.cloud.storage.Storage.StoragePoolType;
2829
import com.cloud.storage.VolumeVO;
2930
import com.cloud.storage.dao.VolumeDao;
3031
import com.cloud.storage.dao.VolumeDaoImpl;
3132
import com.cloud.upgrade.SystemVmTemplateRegistration;
33+
import com.cloud.utils.db.GenericSearchBuilder;
34+
import com.cloud.utils.db.SearchBuilder;
35+
import com.cloud.utils.db.SearchCriteria;
3236
import com.cloud.utils.exception.CloudRuntimeException;
3337

3438
public class Upgrade41700to41710 extends DbUpgradeAbstractImpl implements DbUpgradeSystemVmTemplate {
@@ -95,24 +99,58 @@ public void updateSystemVmTemplates(Connection conn) {
9599
}
96100
}
97101

98-
private void updateStorPoolStorageType() {
99-
storageDao = new PrimaryDataStoreDaoImpl();
100-
List<StoragePoolVO> storPoolPools = storageDao.findPoolsByProvider("StorPool");
101-
for (StoragePoolVO storagePoolVO : storPoolPools) {
102-
if (StoragePoolType.SharedMountPoint == storagePoolVO.getPoolType()) {
103-
storagePoolVO.setPoolType(StoragePoolType.StorPool);
104-
storageDao.update(storagePoolVO.getId(), storagePoolVO);
105-
}
106-
updateStorageTypeForStorPoolVolumes(storagePoolVO.getId());
102+
protected PrimaryDataStoreDao getStorageDao() {
103+
if (storageDao == null) {
104+
storageDao = new PrimaryDataStoreDaoImpl();
107105
}
106+
return storageDao;
108107
}
109108

110-
private void updateStorageTypeForStorPoolVolumes(long storagePoolId) {
111-
volumeDao = new VolumeDaoImpl();
112-
List<VolumeVO> volumes = volumeDao.findByPoolId(storagePoolId, null);
113-
for (VolumeVO volumeVO : volumes) {
114-
volumeVO.setPoolType(StoragePoolType.StorPool);
115-
volumeDao.update(volumeVO.getId(), volumeVO);
109+
protected VolumeDao getVolumeDao() {
110+
if (volumeDao == null) {
111+
volumeDao = new VolumeDaoImpl();
116112
}
113+
return volumeDao;
114+
}
115+
116+
/*
117+
GenericDao.customSearch using GenericSearchBuilder and GenericDao.update using
118+
GenericDao.createSearchBuilder used here to prevent any future issues when new fields
119+
are added to StoragePoolVO or VolumeVO and this upgrade path starts to fail.
120+
*/
121+
protected void updateStorPoolStorageType() {
122+
StoragePoolVO pool = getStorageDao().createForUpdate();
123+
pool.setPoolType(StoragePoolType.StorPool);
124+
SearchBuilder<StoragePoolVO> sb = getStorageDao().createSearchBuilder();
125+
sb.and("provider", sb.entity().getStorageProviderName(), SearchCriteria.Op.EQ);
126+
sb.and("type", sb.entity().getPoolType(), SearchCriteria.Op.EQ);
127+
sb.done();
128+
SearchCriteria<StoragePoolVO> sc = sb.create();
129+
sc.setParameters("provider", StoragePoolType.StorPool.name());
130+
sc.setParameters("type", StoragePoolType.SharedMountPoint.name());
131+
getStorageDao().update(pool, sc);
132+
133+
GenericSearchBuilder<StoragePoolVO, Long> gSb = getStorageDao().createSearchBuilder(Long.class);
134+
gSb.selectFields(gSb.entity().getId());
135+
gSb.and("provider", gSb.entity().getStorageProviderName(), SearchCriteria.Op.EQ);
136+
gSb.done();
137+
SearchCriteria<Long> gSc = gSb.create();
138+
gSc.setParameters("provider", StoragePoolType.StorPool.name());
139+
List<Long> poolIds = getStorageDao().customSearch(gSc, null);
140+
updateStorageTypeForStorPoolVolumes(poolIds);
141+
}
142+
143+
protected void updateStorageTypeForStorPoolVolumes(List<Long> storagePoolIds) {
144+
if (CollectionUtils.isEmpty(storagePoolIds)) {
145+
return;
146+
}
147+
VolumeVO volume = getVolumeDao().createForUpdate();
148+
volume.setPoolType(StoragePoolType.StorPool);
149+
SearchBuilder<VolumeVO> sb = getVolumeDao().createSearchBuilder();
150+
sb.and("poolId", sb.entity().getPoolId(), SearchCriteria.Op.IN);
151+
sb.done();
152+
SearchCriteria<VolumeVO> sc = sb.create();
153+
sc.setParameters("poolId", storagePoolIds.toArray());
154+
getVolumeDao().update(volume, sc);
117155
}
118156
}

engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ public class StoragePoolVO implements StoragePool {
119119
@Column(name = "capacity_iops", updatable = true, nullable = true)
120120
private Long capacityIops;
121121

122+
@Column(name = "used_iops", updatable = true, nullable = true)
123+
private Long usedIops;
124+
122125
@Column(name = "hypervisor")
123126
@Convert(converter = HypervisorTypeConverter.class)
124127
private HypervisorType hypervisor;
@@ -256,6 +259,14 @@ public Long getCapacityIops() {
256259
return capacityIops;
257260
}
258261

262+
public Long getUsedIops() {
263+
return usedIops;
264+
}
265+
266+
public void setUsedIops(Long usedIops) {
267+
this.usedIops = usedIops;
268+
}
269+
259270
@Override
260271
public Long getClusterId() {
261272
return clusterId;

engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.mshost_peer', 'fk_mshost_peer__
3232

3333
-- Add last_id to the volumes table
3434
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'last_id', 'bigint(20) unsigned DEFAULT NULL');
35+
36+
-- Add used_iops column to support IOPS data in storage stats
37+
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.storage_pool', 'used_iops', 'bigint unsigned DEFAULT NULL COMMENT "IOPS currently in use for this storage pool" ');

0 commit comments

Comments
 (0)