This is an automated email from the ASF dual-hosted git repository.
shwstppr pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push:
new bd488c4bba0 server, plugin: enhance storage stats for IOPS (#10034)
bd488c4bba0 is described below
commit bd488c4bba019435515e48075d4437fef5e01aec
Author: Abhishek Kumar <[email protected]>
AuthorDate: Tue Jan 7 17:17:12 2025 +0530
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]>
---
.../main/java/com/cloud/storage/StorageStats.java | 3 +
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
.../api/response/StoragePoolResponse.java | 12 ++
.../com/cloud/agent/api/GetStorageStatsAnswer.java | 36 ++++-
.../cloud/agent/api/GetStorageStatsAnswerTest.java | 81 +++++++++++
debian/control | 2 +-
.../api/storage/PrimaryDataStoreDriver.java | 8 ++
.../com/cloud/upgrade/dao/Upgrade41700to41710.java | 68 +++++++---
.../storage/datastore/db/StoragePoolVO.java | 11 ++
.../resources/META-INF/db/schema-42000to42010.sql | 3 +
.../META-INF/db/views/cloud.storage_pool_view.sql | 2 +
.../cloud/upgrade/dao/Upgrade41700to41710Test.java | 123 +++++++++++++++++
packaging/el8/cloud.spec | 1 +
.../LibvirtGetStorageStatsCommandWrapper.java | 3 +-
.../hypervisor/kvm/storage/KVMStoragePool.java | 8 ++
.../kvm/storage/LibvirtStorageAdaptor.java | 69 ++++++++--
.../hypervisor/kvm/storage/LibvirtStoragePool.java | 28 +++-
.../kvm/storage/LibvirtStorageAdaptorTest.java | 94 ++++++++++++-
.../agent/manager/MockStorageManagerImpl.java | 8 +-
.../com/cloud/simulator/dao/MockVolumeDao.java | 2 +
.../com/cloud/simulator/dao/MockVolumeDaoImpl.java | 12 ++
.../api/query/dao/StoragePoolJoinDaoImpl.java | 9 +-
.../com/cloud/api/query/vo/StoragePoolJoinVO.java | 14 ++
.../main/java/com/cloud/server/StatsCollector.java | 28 +++-
.../java/com/cloud/storage/StorageManagerImpl.java | 23 +++-
.../java/com/cloud/server/StatsCollectorTest.java | 148 +++++++++++++++++----
.../com/cloud/storage/StorageManagerImplTest.java | 96 ++++++++++---
ui/public/locales/en.json | 1 +
ui/src/config/section/infra/primaryStorages.js | 2 +-
29 files changed, 788 insertions(+), 108 deletions(-)
diff --git a/api/src/main/java/com/cloud/storage/StorageStats.java
b/api/src/main/java/com/cloud/storage/StorageStats.java
index a474b23489c..502e2aaae40 100644
--- a/api/src/main/java/com/cloud/storage/StorageStats.java
+++ b/api/src/main/java/com/cloud/storage/StorageStats.java
@@ -26,4 +26,7 @@ public interface StorageStats {
* @return bytes capacity of the storage server
*/
public long getCapacityBytes();
+
+ Long getCapacityIops();
+ Long getUsedIops();
}
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index bf8b79b29d0..cf03f1d2699 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -509,6 +509,7 @@ public class ApiConstants {
public static final String URL = "url";
public static final String USAGE_INTERFACE = "usageinterface";
public static final String USED_SUBNETS = "usedsubnets";
+ public static final String USED_IOPS = "usediops";
public static final String USER_DATA = "userdata";
public static final String USER_DATA_NAME = "userdataname";
diff --git
a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java
b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java
index 06d5103d731..676803ea86b 100644
---
a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java
+++
b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java
@@ -97,6 +97,10 @@ public class StoragePoolResponse extends
BaseResponseWithAnnotations {
@Param(description = "total min IOPS currently in use by volumes")
private Long allocatedIops;
+ @SerializedName(ApiConstants.USED_IOPS)
+ @Param(description = "total IOPS currently in use", since = "4.20.1")
+ private Long usedIops;
+
@SerializedName(ApiConstants.STORAGE_CUSTOM_STATS)
@Param(description = "the storage pool custom stats", since = "4.18.1")
private Map<String, String> customStats;
@@ -312,6 +316,14 @@ public class StoragePoolResponse extends
BaseResponseWithAnnotations {
this.allocatedIops = allocatedIops;
}
+ public Long getUsedIops() {
+ return usedIops;
+ }
+
+ public void setUsedIops(Long usedIops) {
+ this.usedIops = usedIops;
+ }
+
public Map<String, String> getCustomStats() {
return customStats;
}
diff --git a/core/src/main/java/com/cloud/agent/api/GetStorageStatsAnswer.java
b/core/src/main/java/com/cloud/agent/api/GetStorageStatsAnswer.java
index 26e7b749586..79753661066 100644
--- a/core/src/main/java/com/cloud/agent/api/GetStorageStatsAnswer.java
+++ b/core/src/main/java/com/cloud/agent/api/GetStorageStatsAnswer.java
@@ -27,24 +27,46 @@ public class GetStorageStatsAnswer extends Answer
implements StorageStats {
protected GetStorageStatsAnswer() {
}
- protected long used;
+ protected long usedBytes;
- protected long capacity;
+ protected long capacityBytes;
+
+ protected Long capacityIops;
+
+ protected Long usedIops;
@Override
public long getByteUsed() {
- return used;
+ return usedBytes;
}
@Override
public long getCapacityBytes() {
- return capacity;
+ return capacityBytes;
+ }
+
+ @Override
+ public Long getCapacityIops() {
+ return capacityIops;
+ }
+
+ @Override
+ public Long getUsedIops() {
+ return usedIops;
+ }
+
+ public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long
capacityBytes, long usedBytes) {
+ super(cmd, true, null);
+ this.capacityBytes = capacityBytes;
+ this.usedBytes = usedBytes;
}
- public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long capacity,
long used) {
+ public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long
capacityBytes, long usedBytes, Long capacityIops, Long usedIops) {
super(cmd, true, null);
- this.capacity = capacity;
- this.used = used;
+ this.capacityBytes = capacityBytes;
+ this.usedBytes = usedBytes;
+ this.capacityIops = capacityIops;
+ this.usedIops = usedIops;
}
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, String details) {
diff --git
a/core/src/test/java/com/cloud/agent/api/GetStorageStatsAnswerTest.java
b/core/src/test/java/com/cloud/agent/api/GetStorageStatsAnswerTest.java
new file mode 100644
index 00000000000..44af83ada2d
--- /dev/null
+++ b/core/src/test/java/com/cloud/agent/api/GetStorageStatsAnswerTest.java
@@ -0,0 +1,81 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.api;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GetStorageStatsAnswerTest {
+
+ @Test
+ public void testDefaultConstructor() {
+ GetStorageStatsAnswer answer = new GetStorageStatsAnswer();
+
+ Assert.assertEquals(0, answer.getByteUsed());
+ Assert.assertEquals(0, answer.getCapacityBytes());
+ Assert.assertNull(answer.getCapacityIops());
+ Assert.assertNull(answer.getUsedIops());
+ }
+
+ @Test
+ public void testConstructorWithCapacityAndUsedBytes() {
+ GetStorageStatsCommand mockCmd = new GetStorageStatsCommand();
+ long capacityBytes = 1024L;
+ long usedBytes = 512L;
+
+ GetStorageStatsAnswer answer = new GetStorageStatsAnswer(mockCmd,
capacityBytes, usedBytes);
+
+ Assert.assertEquals(capacityBytes, answer.getCapacityBytes());
+ Assert.assertEquals(usedBytes, answer.getByteUsed());
+ Assert.assertNull(answer.getCapacityIops());
+ Assert.assertNull(answer.getUsedIops());
+ }
+
+ @Test
+ public void testConstructorWithIops() {
+ GetStorageStatsCommand mockCmd = new GetStorageStatsCommand();
+ long capacityBytes = 2048L;
+ long usedBytes = 1024L;
+ Long capacityIops = 1000L;
+ Long usedIops = 500L;
+
+ GetStorageStatsAnswer answer = new GetStorageStatsAnswer(mockCmd,
capacityBytes, usedBytes, capacityIops, usedIops);
+
+ Assert.assertEquals(capacityBytes, answer.getCapacityBytes());
+ Assert.assertEquals(usedBytes, answer.getByteUsed());
+ Assert.assertEquals(capacityIops, answer.getCapacityIops());
+ Assert.assertEquals(usedIops, answer.getUsedIops());
+ }
+
+ @Test
+ public void testErrorConstructor() {
+ GetStorageStatsCommand mockCmd = new GetStorageStatsCommand();
+ String errorDetails = "An error occurred";
+
+ GetStorageStatsAnswer answer = new GetStorageStatsAnswer(mockCmd,
errorDetails);
+
+ Assert.assertFalse(answer.getResult());
+ Assert.assertEquals(errorDetails, answer.getDetails());
+ Assert.assertEquals(0, answer.getCapacityBytes());
+ Assert.assertEquals(0, answer.getByteUsed());
+ Assert.assertNull(answer.getCapacityIops());
+ Assert.assertNull(answer.getUsedIops());
+ }
+}
diff --git a/debian/control b/debian/control
index c0cb95af035..a773844c27c 100644
--- a/debian/control
+++ b/debian/control
@@ -24,7 +24,7 @@ Description: CloudStack server library
Package: cloudstack-agent
Architecture: all
-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
+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
Recommends: init-system-helpers
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
Description: CloudStack agent
diff --git
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
index 2011b1f08fb..c8d9015af90 100644
---
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
+++
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
@@ -111,6 +111,14 @@ public interface PrimaryDataStoreDriver extends
DataStoreDriver {
*/
Pair<Long, Long> getStorageStats(StoragePool storagePool);
+ /**
+ * Intended for managed storage
+ * returns the capacity and used IOPS or null if not supported
+ */
+ default Pair<Long, Long> getStorageIopsStats(StoragePool storagePool) {
+ return null;
+ }
+
/**
* intended for managed storage
* returns true if the storage can provide the volume stats (physical and
virtual size)
diff --git
a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41700to41710.java
b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41700to41710.java
index e3eb2bf514d..266401e0c31 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41700to41710.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41700to41710.java
@@ -23,12 +23,16 @@ import java.util.List;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.commons.collections.CollectionUtils;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDaoImpl;
import com.cloud.upgrade.SystemVmTemplateRegistration;
+import com.cloud.utils.db.GenericSearchBuilder;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
public class Upgrade41700to41710 extends DbUpgradeAbstractImpl implements
DbUpgradeSystemVmTemplate {
@@ -95,24 +99,58 @@ public class Upgrade41700to41710 extends
DbUpgradeAbstractImpl implements DbUpgr
}
}
- private void updateStorPoolStorageType() {
- storageDao = new PrimaryDataStoreDaoImpl();
- List<StoragePoolVO> storPoolPools =
storageDao.findPoolsByProvider("StorPool");
- for (StoragePoolVO storagePoolVO : storPoolPools) {
- if (StoragePoolType.SharedMountPoint ==
storagePoolVO.getPoolType()) {
- storagePoolVO.setPoolType(StoragePoolType.StorPool);
- storageDao.update(storagePoolVO.getId(), storagePoolVO);
- }
- updateStorageTypeForStorPoolVolumes(storagePoolVO.getId());
+ protected PrimaryDataStoreDao getStorageDao() {
+ if (storageDao == null) {
+ storageDao = new PrimaryDataStoreDaoImpl();
}
+ return storageDao;
}
- private void updateStorageTypeForStorPoolVolumes(long storagePoolId) {
- volumeDao = new VolumeDaoImpl();
- List<VolumeVO> volumes = volumeDao.findByPoolId(storagePoolId, null);
- for (VolumeVO volumeVO : volumes) {
- volumeVO.setPoolType(StoragePoolType.StorPool);
- volumeDao.update(volumeVO.getId(), volumeVO);
+ protected VolumeDao getVolumeDao() {
+ if (volumeDao == null) {
+ volumeDao = new VolumeDaoImpl();
}
+ return volumeDao;
+ }
+
+ /*
+ GenericDao.customSearch using GenericSearchBuilder and GenericDao.update
using
+ GenericDao.createSearchBuilder used here to prevent any future issues when
new fields
+ are added to StoragePoolVO or VolumeVO and this upgrade path starts to
fail.
+ */
+ protected void updateStorPoolStorageType() {
+ StoragePoolVO pool = getStorageDao().createForUpdate();
+ pool.setPoolType(StoragePoolType.StorPool);
+ SearchBuilder<StoragePoolVO> sb =
getStorageDao().createSearchBuilder();
+ sb.and("provider", sb.entity().getStorageProviderName(),
SearchCriteria.Op.EQ);
+ sb.and("type", sb.entity().getPoolType(), SearchCriteria.Op.EQ);
+ sb.done();
+ SearchCriteria<StoragePoolVO> sc = sb.create();
+ sc.setParameters("provider", StoragePoolType.StorPool.name());
+ sc.setParameters("type", StoragePoolType.SharedMountPoint.name());
+ getStorageDao().update(pool, sc);
+
+ GenericSearchBuilder<StoragePoolVO, Long> gSb =
getStorageDao().createSearchBuilder(Long.class);
+ gSb.selectFields(gSb.entity().getId());
+ gSb.and("provider", gSb.entity().getStorageProviderName(),
SearchCriteria.Op.EQ);
+ gSb.done();
+ SearchCriteria<Long> gSc = gSb.create();
+ gSc.setParameters("provider", StoragePoolType.StorPool.name());
+ List<Long> poolIds = getStorageDao().customSearch(gSc, null);
+ updateStorageTypeForStorPoolVolumes(poolIds);
+ }
+
+ protected void updateStorageTypeForStorPoolVolumes(List<Long>
storagePoolIds) {
+ if (CollectionUtils.isEmpty(storagePoolIds)) {
+ return;
+ }
+ VolumeVO volume = getVolumeDao().createForUpdate();
+ volume.setPoolType(StoragePoolType.StorPool);
+ SearchBuilder<VolumeVO> sb = getVolumeDao().createSearchBuilder();
+ sb.and("poolId", sb.entity().getPoolId(), SearchCriteria.Op.IN);
+ sb.done();
+ SearchCriteria<VolumeVO> sc = sb.create();
+ sc.setParameters("poolId", storagePoolIds.toArray());
+ getVolumeDao().update(volume, sc);
}
}
diff --git
a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
index 92a444bd83f..c2f5d0a5d96 100644
---
a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
+++
b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java
@@ -119,6 +119,9 @@ public class StoragePoolVO implements StoragePool {
@Column(name = "capacity_iops", updatable = true, nullable = true)
private Long capacityIops;
+ @Column(name = "used_iops", updatable = true, nullable = true)
+ private Long usedIops;
+
@Column(name = "hypervisor")
@Convert(converter = HypervisorTypeConverter.class)
private HypervisorType hypervisor;
@@ -256,6 +259,14 @@ public class StoragePoolVO implements StoragePool {
return capacityIops;
}
+ public Long getUsedIops() {
+ return usedIops;
+ }
+
+ public void setUsedIops(Long usedIops) {
+ this.usedIops = usedIops;
+ }
+
@Override
public Long getClusterId() {
return clusterId;
diff --git
a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
index aef99dd0c7f..8b70cce3404 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql
@@ -32,3 +32,6 @@ CALL
`cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.mshost_peer', 'fk_mshost_peer__
-- Add last_id to the volumes table
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'last_id', 'bigint(20)
unsigned DEFAULT NULL');
+
+-- Add used_iops column to support IOPS data in storage stats
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.storage_pool', 'used_iops',
'bigint unsigned DEFAULT NULL COMMENT "IOPS currently in use for this storage
pool" ');
diff --git
a/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql
b/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql
index e6cc9458208..5d7585baa3b 100644
---
a/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql
+++
b/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql
@@ -31,7 +31,9 @@ SELECT
`storage_pool`.`created` AS `created`,
`storage_pool`.`removed` AS `removed`,
`storage_pool`.`capacity_bytes` AS `capacity_bytes`,
+ `storage_pool`.`used_bytes` AS `used_bytes`,
`storage_pool`.`capacity_iops` AS `capacity_iops`,
+ `storage_pool`.`used_iops` AS `used_iops`,
`storage_pool`.`scope` AS `scope`,
`storage_pool`.`hypervisor` AS `hypervisor`,
`storage_pool`.`storage_provider_name` AS `storage_provider_name`,
diff --git
a/engine/schema/src/test/java/com/cloud/upgrade/dao/Upgrade41700to41710Test.java
b/engine/schema/src/test/java/com/cloud/upgrade/dao/Upgrade41700to41710Test.java
new file mode 100644
index 00000000000..ad7c0cede25
--- /dev/null
+++
b/engine/schema/src/test/java/com/cloud/upgrade/dao/Upgrade41700to41710Test.java
@@ -0,0 +1,123 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.upgrade.dao;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.storage.Storage;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDaoImpl;
+import com.cloud.utils.db.GenericSearchBuilder;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+@RunWith(MockitoJUnitRunner.class)
+public class Upgrade41700to41710Test {
+ @Spy
+ Upgrade41700to41710 upgrade41700to41710;
+
+ @Test
+ public void testGetStorageDao_FirstInvocationCreatesInstance() {
+ PrimaryDataStoreDao dao1 = upgrade41700to41710.getStorageDao();
+ Assert.assertNotNull(dao1);
+ Assert.assertTrue(dao1 instanceof PrimaryDataStoreDaoImpl);
+ }
+
+ @Test
+ public void testGetStorageDao_SubsequentInvocationReturnsSameInstance() {
+ PrimaryDataStoreDao dao1 = upgrade41700to41710.getStorageDao();
+ PrimaryDataStoreDao dao2 = upgrade41700to41710.getStorageDao();
+ Assert.assertSame(dao1, dao2);
+ }
+
+ @Test
+ public void testGetVolumeDao_FirstInvocationCreatesInstance() {
+ VolumeDao dao1 = upgrade41700to41710.getVolumeDao();
+ Assert.assertNotNull(dao1);
+ Assert.assertTrue(dao1 instanceof VolumeDaoImpl);
+ }
+
+ @Test
+ public void testGetVolumeDao_SubsequentInvocationReturnsSameInstance() {
+ VolumeDao dao1 = upgrade41700to41710.getVolumeDao();
+ VolumeDao dao2 = upgrade41700to41710.getVolumeDao();
+ Assert.assertSame(dao1, dao2);
+ }
+
+ @Test
+ public void testUpdateStorPoolStorageType_WithPoolIds() {
+ PrimaryDataStoreDao storageDao =
Mockito.mock(PrimaryDataStoreDao.class);
+ Mockito.doReturn(storageDao).when(upgrade41700to41710).getStorageDao();
+ StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
+ SearchBuilder<StoragePoolVO> searchBuilder =
Mockito.mock(SearchBuilder.class);
+
Mockito.when(storageDao.createSearchBuilder()).thenReturn(searchBuilder);
+ Mockito.when(searchBuilder.entity()).thenReturn(pool);
+
Mockito.when(searchBuilder.create()).thenReturn(Mockito.mock(SearchCriteria.class));
+ GenericSearchBuilder<StoragePoolVO, Long> gSb =
Mockito.mock(GenericSearchBuilder.class);
+
Mockito.doReturn(gSb).when(storageDao).createSearchBuilder(Mockito.any());
+
Mockito.when(gSb.create()).thenReturn(Mockito.mock(SearchCriteria.class));
+ Mockito.when(gSb.entity()).thenReturn(pool);
+ Mockito.when(storageDao.createForUpdate()).thenReturn(pool);
+
Mockito.doNothing().when(upgrade41700to41710).updateStorageTypeForStorPoolVolumes(Mockito.any());
+
+ Mockito.when(storageDao.update(Mockito.any(StoragePoolVO.class),
Mockito.any())).thenReturn(2);
+ Mockito.when(storageDao.customSearch(Mockito.any(),
Mockito.any())).thenReturn(List.of(1L, 2L));
+ upgrade41700to41710.updateStorPoolStorageType();
+ Mockito.verify(storageDao,
Mockito.times(1)).update(Mockito.any(StoragePoolVO.class), Mockito.any());
+ Mockito.verify(upgrade41700to41710,
Mockito.times(1)).updateStorageTypeForStorPoolVolumes(Mockito.any());
+ }
+
+ @Test
+ public void testUpdateStorageTypeForStorPoolVolumes_EmptyPoolIds() {
+ VolumeDao volumeDao = Mockito.mock(VolumeDao.class);
+ List<Long> storagePoolIds = Collections.emptyList();
+
upgrade41700to41710.updateStorageTypeForStorPoolVolumes(storagePoolIds);
+ Mockito.verify(volumeDao,
Mockito.never()).update(Mockito.any(VolumeVO.class), Mockito.any());
+ }
+
+ @Test
+ public void testUpdateStorageTypeForStorPoolVolumes_WithPoolIds() {
+ VolumeDao volumeDao = Mockito.mock(VolumeDao.class);
+ List<Long> storagePoolIds = List.of(1L, 2L, 3L);
+ VolumeVO volume = Mockito.mock(VolumeVO.class);
+ SearchBuilder<VolumeVO> searchBuilder =
Mockito.mock(SearchBuilder.class);
+ SearchCriteria<VolumeVO> searchCriteria =
Mockito.mock(SearchCriteria.class);
+ Mockito.when(volumeDao.createForUpdate()).thenReturn(volume);
+
Mockito.when(volumeDao.createSearchBuilder()).thenReturn(searchBuilder);
+ Mockito.when(searchBuilder.entity()).thenReturn(volume);
+ Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
+ Mockito.when(volumeDao.update(Mockito.any(VolumeVO.class),
Mockito.any())).thenReturn(3);
+ Mockito.doReturn(volumeDao).when(upgrade41700to41710).getVolumeDao();
+
upgrade41700to41710.updateStorageTypeForStorPoolVolumes(storagePoolIds);
+ Mockito.verify(volumeDao).createForUpdate();
+ Mockito.verify(volume).setPoolType(Storage.StoragePoolType.StorPool);
+ Mockito.verify(volumeDao).update(Mockito.eq(volume),
Mockito.eq(searchCriteria));
+ Mockito.verify(searchCriteria).setParameters("poolId",
storagePoolIds.toArray());
+ }
+}
diff --git a/packaging/el8/cloud.spec b/packaging/el8/cloud.spec
index eb03cfe0df4..e34778820cb 100644
--- a/packaging/el8/cloud.spec
+++ b/packaging/el8/cloud.spec
@@ -118,6 +118,7 @@ Requires: cryptsetup
Requires: rng-tools
Requires: (libgcrypt > 1.8.3 or libgcrypt20)
Requires: (selinux-tools if qemu-tools)
+Requires: sysstat
Provides: cloud-agent
Group: System Environment/Libraries
%description agent
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetStorageStatsCommandWrapper.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetStorageStatsCommandWrapper.java
index d00f5b540e2..419b5449258 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetStorageStatsCommandWrapper.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetStorageStatsCommandWrapper.java
@@ -40,7 +40,8 @@ public final class LibvirtGetStorageStatsCommandWrapper
extends CommandWrapper<G
if (sp == null) {
return new GetStorageStatsAnswer(command, "no storage pool to
get statistics from");
}
- return new GetStorageStatsAnswer(command, sp.getCapacity(),
sp.getUsed());
+ return new GetStorageStatsAnswer(command, sp.getCapacity(),
sp.getUsed(), sp.getCapacityIops(),
+ sp.getUsedIops());
} catch (final CloudRuntimeException e) {
return new GetStorageStatsAnswer(command, e.toString());
}
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
index 674799c0bbe..d7791310ff7 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java
@@ -62,6 +62,14 @@ public interface KVMStoragePool {
public long getUsed();
+ default Long getCapacityIops() {
+ return null;
+ }
+
+ default Long getUsedIops() {
+ return null;
+ }
+
public long getAvailable();
public boolean refresh();
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
index a9dfd29a4bf..f3731459f89 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java
@@ -16,14 +16,21 @@
// under the License.
package com.cloud.hypervisor.kvm.storage;
+import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
+
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.utils.cryptsetup.KeyFile;
@@ -33,9 +40,10 @@ import org.apache.cloudstack.utils.qemu.QemuImgException;
import org.apache.cloudstack.utils.qemu.QemuImgFile;
import org.apache.cloudstack.utils.qemu.QemuObject;
import org.apache.commons.codec.binary.Base64;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.LogManager;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.LibvirtException;
import org.libvirt.Secret;
@@ -69,14 +77,6 @@ import com.cloud.storage.StorageLayer;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script;
-import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-
public class LibvirtStorageAdaptor implements StorageAdaptor {
protected Logger logger = LogManager.getLogger(getClass());
private StorageLayer _storageLayer;
@@ -521,6 +521,54 @@ public class LibvirtStorageAdaptor implements
StorageAdaptor {
return this.getStoragePool(uuid, false);
}
+ protected void updateLocalPoolIops(LibvirtStoragePool pool) {
+ if (!StoragePoolType.Filesystem.equals(pool.getType()) ||
StringUtils.isBlank(pool.getLocalPath())) {
+ return;
+ }
+ logger.trace("Updating used IOPS for pool: {}", pool.getName());
+
+ // Run script to get data
+ List<String[]> commands = new ArrayList<>();
+ commands.add(new String[]{
+ Script.getExecutableAbsolutePath("bash"),
+ "-c",
+ String.format(
+ "%s %s | %s 'NR==2 {print $1}'",
+ Script.getExecutableAbsolutePath("df"),
+ pool.getLocalPath(),
+ Script.getExecutableAbsolutePath("awk")
+ )
+ });
+ String result = Script.executePipedCommands(commands, 1000).second();
+ if (StringUtils.isBlank(result)) {
+ return;
+ }
+ result = result.trim();
+ commands.add(new String[]{
+ Script.getExecutableAbsolutePath("bash"),
+ "-c",
+ String.format(
+ "%s -z %s 1 2 | %s 'NR==7 {print $2}'",
+ Script.getExecutableAbsolutePath("iostat"),
+ result,
+ Script.getExecutableAbsolutePath("awk")
+ )
+ });
+ result = Script.executePipedCommands(commands, 10000).second();
+ logger.trace("Pool used IOPS result: {}", result);
+ if (StringUtils.isBlank(result)) {
+ return;
+ }
+ try {
+ double doubleValue = Double.parseDouble(result);
+ pool.setUsedIops((long) doubleValue);
+ logger.debug("Updated used IOPS: {} for pool: {}",
pool.getUsedIops(), pool.getName());
+ } catch (NumberFormatException e) {
+ logger.warn(String.format("Unable to parse retrieved used IOPS: %s
for pool: %s", result,
+ pool.getName()));
+ }
+ }
+
@Override
public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
logger.info("Trying to fetch storage pool " + uuid + " from libvirt");
@@ -591,6 +639,7 @@ public class LibvirtStorageAdaptor implements
StorageAdaptor {
}
pool.setCapacity(storage.getInfo().capacity);
pool.setUsed(storage.getInfo().allocation);
+ updateLocalPoolIops(pool);
pool.setAvailable(storage.getInfo().available);
logger.debug("Successfully refreshed pool " + uuid +
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
index 560020cad38..8e5af7c613d 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
@@ -43,6 +43,8 @@ public class LibvirtStoragePool implements KVMStoragePool {
protected String uuid;
protected long capacity;
protected long used;
+ protected Long capacityIops;
+ protected Long usedIops;
protected long available;
protected String name;
protected String localPath;
@@ -81,20 +83,38 @@ public class LibvirtStoragePool implements KVMStoragePool {
this.used = used;
}
- public void setAvailable(long available) {
- this.available = available;
- }
-
@Override
public long getUsed() {
return this.used;
}
+ @Override
+ public Long getCapacityIops() {
+ return capacityIops;
+ }
+
+ public void setCapacityIops(Long capacityIops) {
+ this.capacityIops = capacityIops;
+ }
+
+ @Override
+ public Long getUsedIops() {
+ return usedIops;
+ }
+
+ public void setUsedIops(Long usedIops) {
+ this.usedIops = usedIops;
+ }
+
@Override
public long getAvailable() {
return this.available;
}
+ public void setAvailable(long available) {
+ this.available = available;
+ }
+
public StoragePoolType getStoragePoolType() {
return this.type;
}
diff --git
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptorTest.java
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptorTest.java
index c2bbff7efb0..88346abd017 100644
---
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptorTest.java
+++
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptorTest.java
@@ -17,18 +17,22 @@
package com.cloud.hypervisor.kvm.storage;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
-import com.cloud.utils.exception.CloudRuntimeException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libvirt.Connect;
import org.libvirt.StoragePool;
-import org.libvirt.StoragePoolInfo;
+import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -38,6 +42,9 @@ import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.hypervisor.kvm.resource.LibvirtStoragePoolDef;
import com.cloud.storage.Storage;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtStorageAdaptorTest {
@@ -46,6 +53,11 @@ public class LibvirtStorageAdaptorTest {
private AutoCloseable closeable;
+ @Mock
+ LibvirtStoragePool mockPool;
+
+ MockedStatic<Script> mockScript;
+
@Spy
static LibvirtStorageAdaptor libvirtStorageAdaptor = new
LibvirtStorageAdaptor(null);
@@ -53,11 +65,14 @@ public class LibvirtStorageAdaptorTest {
public void initMocks() {
closeable = MockitoAnnotations.openMocks(this);
libvirtConnectionMockedStatic =
Mockito.mockStatic(LibvirtConnection.class);
+ Mockito.reset(mockPool);
+ mockScript = Mockito.mockStatic(Script.class);
}
@After
public void tearDown() throws Exception {
libvirtConnectionMockedStatic.close();
+ mockScript.close();
closeable.close();
}
@@ -78,14 +93,87 @@ public class LibvirtStorageAdaptorTest {
Connect conn = Mockito.mock(Connect.class);
StoragePool sp = Mockito.mock(StoragePool.class);
- StoragePoolInfo spinfo = Mockito.mock(StoragePoolInfo.class);
Mockito.when(LibvirtConnection.getConnection()).thenReturn(conn);
Mockito.when(conn.storagePoolLookupByUUIDString(uuid)).thenReturn(sp);
Mockito.when(sp.isActive()).thenReturn(1);
Mockito.when(sp.getXMLDesc(0)).thenReturn(poolXml);
+
Mockito.when(Script.runSimpleBashScriptForExitValue(anyString())).thenReturn(-1);
Map<String, String> details = new HashMap<>();
details.put("nfsmountopts", "vers=4.1, nconnect=4");
KVMStoragePool pool = libvirtStorageAdaptor.createStoragePool(uuid,
null, 0, dir, null, Storage.StoragePoolType.NetworkFilesystem, details, true);
}
+
+ @Test
+ public void testUpdateLocalPoolIops_IgnoredForNonFilesystemType() {
+
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.SharedMountPoint);
+
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+ }
+
+ @Test
+ public void testUpdateLocalPoolIops_IgnoredForBlankLocalPath() {
+
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.Filesystem);
+ Mockito.when(mockPool.getLocalPath()).thenReturn("");
+
+ Mockito.verify(mockPool, never()).getLocalPath();
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+
+ Mockito.verify(mockPool, never()).setUsedIops(anyLong());
+ }
+
+ @Test
+ public void testUpdateLocalPoolIops_NoDevice() {
+
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.Filesystem);
+ Mockito.when(mockPool.getLocalPath()).thenReturn("/mock/path");
+ Mockito.when(mockPool.getName()).thenReturn("mockPool");
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(1000L))).thenReturn(new Pair<>(0, "\n"));
+
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+
+ Mockito.verify(mockPool, never()).setUsedIops(anyLong());
+ }
+
+ @Test
+ public void testUpdateLocalPoolIops_SuccessfulUpdate() {
+
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.Filesystem);
+ Mockito.when(mockPool.getLocalPath()).thenReturn("/mock/path");
+ Mockito.when(mockPool.getName()).thenReturn("mockPool");
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(1000L))).thenReturn(new Pair<>(0, "sda\n"));
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(10000L))).thenReturn(new Pair<>(0, "42\n"));
+
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+
+ Mockito.verify(mockPool).setUsedIops(42L);
+ }
+
+ @Test
+ public void testUpdateLocalPoolIops_HandlesNumberFormatException() {
+
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.Filesystem);
+ Mockito.when(mockPool.getLocalPath()).thenReturn("/mock/path");
+ Mockito.when(mockPool.getName()).thenReturn("mockPool");
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(1000L))).thenReturn(new Pair<>(0, "sda\n"));
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(10000L)))
+ .thenReturn(new Pair<>(0, "invalid_number"));
+
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+
+ Mockito.verify(mockPool, never()).setUsedIops(anyLong());
+ }
+
+ @Test
+ public void testUpdateLocalPoolIops_NullResultFromScript() {
+
Mockito.when(mockPool.getType()).thenReturn(Storage.StoragePoolType.Filesystem);
+ Mockito.when(mockPool.getLocalPath()).thenReturn("/mock/path");
+ Mockito.when(mockPool.getName()).thenReturn("mockPool");
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(1000L))).thenReturn(new Pair<>(0, "sda\n"));
+ Mockito.when(Script.executePipedCommands(anyList(),
Mockito.eq(10000L)))
+ .thenReturn(new Pair<>(0, null));
+
+ libvirtStorageAdaptor.updateLocalPoolIops(mockPool);
+
+ Mockito.verify(mockPool, never()).setUsedIops(anyLong());
+ }
}
diff --git
a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockStorageManagerImpl.java
b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockStorageManagerImpl.java
index 461347f47cd..4ade80a8fb2 100644
---
a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockStorageManagerImpl.java
+++
b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockStorageManagerImpl.java
@@ -32,13 +32,14 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
-import org.springframework.stereotype.Component;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.command.DownloadCommand;
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
import org.apache.cloudstack.storage.command.UploadStatusAnswer;
import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
import org.apache.cloudstack.storage.command.UploadStatusCommand;
+import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
+import org.springframework.stereotype.Component;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.AttachIsoCommand;
@@ -639,8 +640,11 @@ public class MockStorageManagerImpl extends ManagerBase
implements MockStorageMa
if (totalUsed == null) {
totalUsed = 0L;
}
+ // Mock IOPS stats
+ long capacityIops =
Math.min(Math.max(ByteScaleUtils.bytesToGibibytes(pool.getCapacity()), 1),
1000) * 1000L;
+ long usedIops = capacityIops / 2;
txn.commit();
- return new GetStorageStatsAnswer(cmd, pool.getCapacity(),
totalUsed);
+ return new GetStorageStatsAnswer(cmd, pool.getCapacity(),
totalUsed, capacityIops, usedIops);
}
} catch (Exception ex) {
txn.rollback();
diff --git
a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDao.java
b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDao.java
index b35b8488157..483282b23b2 100644
---
a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDao.java
+++
b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDao.java
@@ -31,4 +31,6 @@ public interface MockVolumeDao extends
GenericDao<MockVolumeVO, Long> {
public MockVolumeVO findByName(String volumeName);
Long findTotalStorageId(long id);
+
+ int countForStorageId(long id);
}
diff --git
a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDaoImpl.java
b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDaoImpl.java
index 5ebccb8161e..417b64dffe1 100644
---
a/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDaoImpl.java
+++
b/plugins/hypervisors/simulator/src/main/java/com/cloud/simulator/dao/MockVolumeDaoImpl.java
@@ -36,6 +36,7 @@ public class MockVolumeDaoImpl extends
GenericDaoBase<MockVolumeVO, Long> implem
protected final SearchBuilder<MockVolumeVO> namePoolSearch;
protected final SearchBuilder<MockVolumeVO> nameSearch;
protected final GenericSearchBuilder<MockVolumeVO, Long> totalSearch;
+ protected final SearchBuilder<MockVolumeVO> countSearch;
@Override
public List<MockVolumeVO> findByStorageIdAndType(long id, MockVolumeType
type) {
@@ -53,6 +54,13 @@ public class MockVolumeDaoImpl extends
GenericDaoBase<MockVolumeVO, Long> implem
return customSearch(sc, null).get(0);
}
+ @Override
+ public int countForStorageId(long id) {
+ SearchCriteria<MockVolumeVO> sc = countSearch.create();
+ sc.setParameters("poolId", id);
+ return getCount(sc);
+ }
+
@Override
public MockVolumeVO findByStoragePathAndType(String path) {
SearchCriteria<MockVolumeVO> sc = pathTypeSearch.create();
@@ -99,5 +107,9 @@ public class MockVolumeDaoImpl extends
GenericDaoBase<MockVolumeVO, Long> implem
totalSearch.and("poolId", totalSearch.entity().getPoolId(),
SearchCriteria.Op.EQ);
totalSearch.done();
+ countSearch = createSearchBuilder();
+ countSearch.and("poolId", countSearch.entity().getPoolId(),
SearchCriteria.Op.EQ);
+ countSearch.done();
+
}
}
diff --git
a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
index c401cabe233..89bfaf24766 100644
--- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
@@ -143,7 +143,9 @@ public class StoragePoolJoinDaoImpl extends
GenericDaoBase<StoragePoolJoinVO, Lo
}
poolResponse.setDiskSizeTotal(pool.getCapacityBytes());
poolResponse.setDiskSizeAllocated(allocatedSize);
+ poolResponse.setDiskSizeUsed(pool.getUsedBytes());
poolResponse.setCapacityIops(pool.getCapacityIops());
+ poolResponse.setUsedIops(pool.getUsedIops());
if (storagePool.isManaged()) {
DataStore store = dataStoreMgr.getDataStore(pool.getId(),
DataStoreRole.Primary);
@@ -159,13 +161,6 @@ public class StoragePoolJoinDaoImpl extends
GenericDaoBase<StoragePoolJoinVO, Lo
}
}
- // TODO: StatsCollector does not persist data
- StorageStats stats = ApiDBUtils.getStoragePoolStatistics(pool.getId());
- if (stats != null) {
- Long used = stats.getByteUsed();
- poolResponse.setDiskSizeUsed(used);
- }
-
poolResponse.setClusterId(pool.getClusterUuid());
poolResponse.setClusterName(pool.getClusterName());
poolResponse.setProvider(pool.getStorageProviderName());
diff --git a/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
b/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
index 762f4a14fe5..41a30fd40d3 100644
--- a/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java
@@ -79,6 +79,9 @@ public class StoragePoolJoinVO extends BaseViewVO implements
InternalIdentity, I
@Column(name = "capacity_bytes")
private long capacityBytes;
+ @Column(name = "used_bytes")
+ private long usedBytes;
+
@Column(name = "cluster_id")
private long clusterId;
@@ -138,6 +141,9 @@ public class StoragePoolJoinVO extends BaseViewVO
implements InternalIdentity, I
@Column(name = "capacity_iops")
private Long capacityIops;
+ @Column(name = "used_iops")
+ private Long usedIops;
+
@Column(name = "hypervisor")
@Convert(converter = HypervisorTypeConverter.class)
private HypervisorType hypervisor;
@@ -201,10 +207,18 @@ public class StoragePoolJoinVO extends BaseViewVO
implements InternalIdentity, I
return capacityBytes;
}
+ public long getUsedBytes() {
+ return usedBytes;
+ }
+
public Long getCapacityIops() {
return capacityIops;
}
+ public Long getUsedIops() {
+ return usedIops;
+ }
+
public long getClusterId() {
return clusterId;
}
diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java
b/server/src/main/java/com/cloud/server/StatsCollector.java
index a5f91b1b3f3..2bdc008ca1a 100644
--- a/server/src/main/java/com/cloud/server/StatsCollector.java
+++ b/server/src/main/java/com/cloud/server/StatsCollector.java
@@ -1723,11 +1723,14 @@ public class StatsCollector extends ManagerBase
implements ComponentMethodInterc
try {
Answer answer = _storageManager.sendToPool(pool,
command);
if (answer != null && answer.getResult()) {
- storagePoolStats.put(pool.getId(),
(StorageStats)answer);
+ StorageStats stats = (StorageStats)answer;
+ storagePoolStats.put(pool.getId(), stats);
boolean poolNeedsUpdating = false;
- long capacityBytes =
((StorageStats)answer).getCapacityBytes();
- long usedBytes =
((StorageStats)answer).getByteUsed();
+ long capacityBytes = stats.getCapacityBytes();
+ long usedBytes = stats.getByteUsed();
+ Long capacityIops = stats.getCapacityIops();
+ Long usedIops = stats.getUsedIops();
// Seems like we have dynamically updated the pool
size since the prev. size and the current do not match
if ((_storagePoolStats.get(poolId) != null &&
_storagePoolStats.get(poolId).getCapacityBytes() != capacityBytes)
|| pool.getCapacityBytes() !=
capacityBytes) {
@@ -1744,6 +1747,7 @@ public class StatsCollector extends ManagerBase
implements ComponentMethodInterc
pool.setUsedBytes(usedBytes);
poolNeedsUpdating = true;
}
+ poolNeedsUpdating =
isPoolNeedsIopsStatsUpdate(pool, capacityIops, usedIops) || poolNeedsUpdating;
if (poolNeedsUpdating) {
pool.setUpdateTime(new Date());
_storagePoolDao.update(pool.getId(), pool);
@@ -1775,6 +1779,24 @@ public class StatsCollector extends ManagerBase
implements ComponentMethodInterc
}
}
+ protected boolean isPoolNeedsIopsStatsUpdate(StoragePoolVO pool, Long
capacityIops, Long usedIops) {
+ boolean poolNeedsUpdating = false;
+ long poolId = pool.getId();
+ if (capacityIops != null && ((_storagePoolStats.get(poolId) != null &&
+
!capacityIops.equals(_storagePoolStats.get(poolId).getCapacityIops())) ||
+ !capacityIops.equals(pool.getCapacityIops()))) {
+ pool.setCapacityIops(capacityIops);
+ poolNeedsUpdating = true;
+ }
+ if (usedIops != null && ((_storagePoolStats.get(poolId) != null &&
+ !usedIops.equals(_storagePoolStats.get(poolId).getUsedIops()))
||
+ !usedIops.equals(pool.getUsedIops()))) {
+ pool.setUsedIops(usedIops);
+ poolNeedsUpdating = true;
+ }
+ return poolNeedsUpdating;
+ }
+
class AutoScaleMonitor extends ManagedContextRunnable {
@Override
protected void runInContext() {
diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
index 7b14cae151e..b0c208a3842 100644
--- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
@@ -553,20 +553,29 @@ public class StorageManagerImpl extends ManagerBase
implements StorageManager, C
return answers[0];
}
- private GetStorageStatsAnswer getStoragePoolStats(StoragePool pool,
GetStorageStatsCommand cmd) {
- GetStorageStatsAnswer answer = null;
+ protected Pair<Long, Long> getStoragePoolIopsStats(PrimaryDataStoreDriver
primaryStoreDriver, StoragePool pool) {
+ Pair<Long, Long> result = primaryStoreDriver.getStorageIopsStats(pool);
+ if (result != null) {
+ return result;
+ }
+ Long usedIops = primaryStoreDriver.getUsedIops(pool);
+ if (usedIops <= 0) {
+ usedIops = null;
+ }
+ return new Pair<>(pool.getCapacityIops(), usedIops);
+ }
+ private GetStorageStatsAnswer getStoragePoolStats(StoragePool pool,
GetStorageStatsCommand cmd) {
DataStoreProvider storeProvider =
_dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName());
DataStoreDriver storeDriver = storeProvider.getDataStoreDriver();
PrimaryDataStoreDriver primaryStoreDriver = (PrimaryDataStoreDriver)
storeDriver;
Pair<Long, Long> storageStats =
primaryStoreDriver.getStorageStats(pool);
if (storageStats == null) {
- answer = new GetStorageStatsAnswer((GetStorageStatsCommand) cmd,
"Failed to get storage stats for pool: " + pool.getId());
- } else {
- answer = new GetStorageStatsAnswer((GetStorageStatsCommand) cmd,
storageStats.first(), storageStats.second());
+ return new GetStorageStatsAnswer(cmd, "Failed to get storage stats
for pool: " + pool.getId());
}
-
- return answer;
+ Pair<Long, Long> iopsStats =
getStoragePoolIopsStats(primaryStoreDriver, pool);
+ return new GetStorageStatsAnswer(cmd, storageStats.first(),
storageStats.second(),
+ iopsStats.first(), iopsStats.second());
}
@Override
diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java
b/server/src/test/java/com/cloud/server/StatsCollectorTest.java
index 2b2451c66c7..6a979259cd9 100644
--- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java
+++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java
@@ -18,21 +18,23 @@
//
package com.cloud.server;
-import com.cloud.agent.api.VmDiskStatsEntry;
-import com.cloud.agent.api.VmStatsEntry;
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.server.StatsCollector.ExternalStatsProtocol;
-import com.cloud.storage.VolumeStatsVO;
-import com.cloud.storage.dao.VolumeStatsDao;
-import com.cloud.user.VmDiskStatisticsVO;
-import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.vm.VmStats;
-import com.cloud.vm.VmStatsVO;
-import com.cloud.vm.dao.VmStatsDaoImpl;
-import com.google.gson.Gson;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import static org.mockito.Mockito.when;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.collections.CollectionUtils;
import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
@@ -52,20 +54,25 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import org.springframework.test.util.ReflectionTestUtils;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-
-import static org.mockito.Mockito.when;
+import com.cloud.agent.api.GetStorageStatsAnswer;
+import com.cloud.agent.api.GetStorageStatsCommand;
+import com.cloud.agent.api.VmDiskStatsEntry;
+import com.cloud.agent.api.VmStatsEntry;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.server.StatsCollector.ExternalStatsProtocol;
+import com.cloud.storage.StorageStats;
+import com.cloud.storage.VolumeStatsVO;
+import com.cloud.storage.dao.VolumeStatsDao;
+import com.cloud.user.VmDiskStatisticsVO;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VmStats;
+import com.cloud.vm.VmStatsVO;
+import com.cloud.vm.dao.VmStatsDaoImpl;
+import com.google.gson.Gson;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
@RunWith(DataProviderRunner.class)
public class StatsCollectorTest {
@@ -104,6 +111,9 @@ public class StatsCollectorTest {
@Mock
VolumeStatsDao volumeStatsDao = Mockito.mock(VolumeStatsDao.class);
+ @Mock
+ private StoragePoolVO mockPool;
+
private static Gson gson = new Gson();
private MockedStatic<InfluxDBFactory> influxDBFactoryMocked;
@@ -515,11 +525,91 @@ public class StatsCollectorTest {
private Map<String, String> convertJsonToOrderedMap(String json) {
Map<String, String> jsonMap = new TreeMap<String, String>();
- String[] keyValuePairs = json.replace("{",
"").replace("}","").split(",");
- for (String pair: keyValuePairs) {
+ String[] keyValuePairs = json.replace("{", "").replace("}",
"").split(",");
+ for (String pair : keyValuePairs) {
String[] keyValue = pair.split(":");
jsonMap.put(keyValue[0], keyValue[1]);
}
return jsonMap;
}
+
+ private void setCollectorIopsStats(long poolId, Long capacityIops, Long
usedIops) {
+ ConcurrentHashMap<Long, StorageStats> storagePoolStats = new
ConcurrentHashMap<>();
+ storagePoolStats.put(poolId, new
GetStorageStatsAnswer(Mockito.mock(GetStorageStatsCommand.class),
+ 10L, 2L, capacityIops, usedIops));
+ ReflectionTestUtils.setField(statsCollector, "_storagePoolStats",
storagePoolStats);
+ }
+
+ @Test
+ public void testPoolNeedsIopsStatsUpdating_NoChanges() {
+ long poolId = 1L;
+ long capacityIops = 100L;
+ long usedIops = 50L;
+ when(mockPool.getId()).thenReturn(poolId);
+ when(mockPool.getCapacityIops()).thenReturn(capacityIops);
+ when(mockPool.getUsedIops()).thenReturn(usedIops);
+
+ setCollectorIopsStats(poolId, capacityIops, usedIops);
+
+ boolean result = statsCollector.isPoolNeedsIopsStatsUpdate(mockPool,
capacityIops, usedIops);
+ Assert.assertFalse(result);
+ Mockito.verify(mockPool,
Mockito.never()).setCapacityIops(Mockito.anyLong());
+ Mockito.verify(mockPool,
Mockito.never()).setUsedIops(Mockito.anyLong());
+ }
+
+ @Test
+ public void testPoolNeedsIopsStatsUpdating_CapacityIopsNeedsUpdating() {
+ long poolId = 1L;
+ when(mockPool.getId()).thenReturn(poolId);
+ when(mockPool.getCapacityIops()).thenReturn(100L);
+ when(mockPool.getUsedIops()).thenReturn(50L);
+
+ setCollectorIopsStats(poolId, 90L, 50L);
+
+ boolean result = statsCollector.isPoolNeedsIopsStatsUpdate(mockPool,
120L, 50L);
+ Assert.assertTrue(result);
+ Mockito.verify(mockPool).setCapacityIops(120L);
+ Mockito.verify(mockPool,
Mockito.never()).setUsedIops(Mockito.anyLong());
+ }
+
+ @Test
+ public void testPoolNeedsIopsStatsUpdating_UsedIopsNeedsUpdating() {
+ long poolId = 1L;
+ when(mockPool.getId()).thenReturn(poolId);
+ when(mockPool.getCapacityIops()).thenReturn(100L);
+ when(mockPool.getUsedIops()).thenReturn(50L);
+
+ setCollectorIopsStats(poolId, 100L, 45L);
+
+ boolean result = statsCollector.isPoolNeedsIopsStatsUpdate(mockPool,
100L, 60L);
+ Assert.assertTrue(result);
+ Mockito.verify(mockPool).setUsedIops(60L);
+ Mockito.verify(mockPool,
Mockito.never()).setCapacityIops(Mockito.anyLong());
+ }
+
+ @Test
+ public void testPoolNeedsIopsStatsUpdating_BothNeedUpdating() {
+ long poolId = 1L;
+ when(mockPool.getId()).thenReturn(poolId);
+ when(mockPool.getCapacityIops()).thenReturn(100L);
+ when(mockPool.getUsedIops()).thenReturn(50L);
+
+ setCollectorIopsStats(poolId, 90L, 45L);
+
+ boolean result = statsCollector.isPoolNeedsIopsStatsUpdate(mockPool,
120L, 60L);
+ Assert.assertTrue(result);
+ Mockito.verify(mockPool).setCapacityIops(120L);
+ Mockito.verify(mockPool).setUsedIops(60L);
+ }
+
+ @Test
+ public void testPoolNeedsIopsStatsUpdating_NullIops() {
+ long poolId = 1L;
+ when(mockPool.getId()).thenReturn(poolId);
+
+ boolean result = statsCollector.isPoolNeedsIopsStatsUpdate(mockPool,
null, null);
+ Assert.assertFalse(result);
+ Mockito.verify(mockPool,
Mockito.never()).setCapacityIops(Mockito.anyLong());
+ Mockito.verify(mockPool,
Mockito.never()).setUsedIops(Mockito.anyLong());
+ }
}
diff --git a/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
b/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
index 98a6e203ed7..4a28e044d9c 100644
--- a/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
+++ b/server/src/test/java/com/cloud/storage/StorageManagerImplTest.java
@@ -22,26 +22,10 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import
org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd;
-import com.cloud.agent.api.StoragePoolInfo;
-import com.cloud.dc.ClusterVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.dc.DataCenterVO;
-import com.cloud.dc.dao.ClusterDao;
-import com.cloud.dc.dao.DataCenterDao;
-import com.cloud.exception.ConnectionException;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.host.Host;
-import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.storage.dao.VolumeDao;
-import com.cloud.user.AccountManagerImpl;
-import com.cloud.utils.Pair;
-import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.vm.VMInstanceVO;
-import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.ApiConstants;
+import
org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd;
+import
org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.framework.config.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -65,14 +49,31 @@ import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Command;
+import com.cloud.agent.api.StoragePoolInfo;
import com.cloud.capacity.CapacityManager;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenterVO;
import com.cloud.dc.VsphereStoragePolicyVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.VsphereStoragePolicyDao;
import com.cloud.exception.AgentUnavailableException;
+import com.cloud.exception.ConnectionException;
+import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.OperationTimedoutException;
+import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.StorageUnavailableException;
+import com.cloud.host.Host;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.HypervisorGuruManager;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.user.AccountManagerImpl;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.DiskProfile;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.dao.VMInstanceDao;
@RunWith(MockitoJUnitRunner.class)
public class StorageManagerImplTest {
@@ -835,4 +836,63 @@ public class StorageManagerImplTest {
boolean result = storageManagerImpl.checkPoolforSpace(pool,
allocatedSizeWithTemplate, totalAskingSize, true);
Assert.assertTrue(result);
}
+
+ @Test
+ public void testGetStoragePoolIopsStats_ReturnsDriverResultWhenNotNull() {
+ StoragePool pool = Mockito.mock(StoragePool.class);
+ PrimaryDataStoreDriver primaryStoreDriver =
Mockito.mock(PrimaryDataStoreDriver.class);
+ Pair<Long, Long> expectedResult = new Pair<>(1000L, 500L);
+
Mockito.when(primaryStoreDriver.getStorageIopsStats(pool)).thenReturn(expectedResult);
+
+ Pair<Long, Long> result =
storageManagerImpl.getStoragePoolIopsStats(primaryStoreDriver, pool);
+
+ Assert.assertSame("Should return the result from
primaryStoreDriver.getStorageIopsStats", expectedResult, result);
+ Mockito.verify(primaryStoreDriver,
Mockito.never()).getUsedIops(Mockito.any());
+ Mockito.verify(pool, Mockito.never()).getCapacityIops();
+ }
+
+ @Test
+ public void testGetStoragePoolIopsStats_UsedIopsPositive() {
+ StoragePool pool = Mockito.mock(StoragePool.class);
+ PrimaryDataStoreDriver primaryStoreDriver =
Mockito.mock(PrimaryDataStoreDriver.class);
+
Mockito.when(primaryStoreDriver.getStorageIopsStats(pool)).thenReturn(null);
+ Mockito.when(primaryStoreDriver.getUsedIops(pool)).thenReturn(500L);
+ Mockito.when(pool.getCapacityIops()).thenReturn(1000L);
+
+ Pair<Long, Long> result =
storageManagerImpl.getStoragePoolIopsStats(primaryStoreDriver, pool);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals("Capacity IOPS should match pool's capacity IOPS",
1000L, result.first().longValue());
+ Assert.assertEquals("Used IOPS should match the positive value
returned", 500L, result.second().longValue());
+ }
+
+ @Test
+ public void testGetStoragePoolIopsStats_UsedIopsZero() {
+ StoragePool pool = Mockito.mock(StoragePool.class);
+ PrimaryDataStoreDriver primaryStoreDriver =
Mockito.mock(PrimaryDataStoreDriver.class);
+
Mockito.when(primaryStoreDriver.getStorageIopsStats(pool)).thenReturn(null);
+ Mockito.when(primaryStoreDriver.getUsedIops(pool)).thenReturn(0L);
+ Mockito.when(pool.getCapacityIops()).thenReturn(1000L);
+
+ Pair<Long, Long> result =
storageManagerImpl.getStoragePoolIopsStats(primaryStoreDriver, pool);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals("Capacity IOPS should match pool's capacity IOPS",
1000L, result.first().longValue());
+ Assert.assertNull("Used IOPS should be null when usedIops <= 0",
result.second());
+ }
+
+ @Test
+ public void testGetStoragePoolIopsStats_UsedIopsNegative() {
+ StoragePool pool = Mockito.mock(StoragePool.class);
+ PrimaryDataStoreDriver primaryStoreDriver =
Mockito.mock(PrimaryDataStoreDriver.class);
+
Mockito.when(primaryStoreDriver.getStorageIopsStats(pool)).thenReturn(null);
+ Mockito.when(primaryStoreDriver.getUsedIops(pool)).thenReturn(-100L);
+ Mockito.when(pool.getCapacityIops()).thenReturn(1000L);
+
+ Pair<Long, Long> result =
storageManagerImpl.getStoragePoolIopsStats(primaryStoreDriver, pool);
+
+ Assert.assertNotNull(result);
+ Assert.assertEquals("Capacity IOPS should match pool's capacity IOPS",
1000L, result.first().longValue());
+ Assert.assertNull("Used IOPS should be null when usedIops <= 0",
result.second());
+ }
}
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index c89b8746e54..db20b859cf7 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -2131,6 +2131,7 @@
"label.srx.firewall": "Juniper SRX firewall",
"label.ssh.key.pairs": "SSH key pairs",
"label.uefi.supported": "UEFI supported",
+"label.usediops": "IOPS used",
"label.userdataid": "Userdata ID",
"label.userdataname": "Userdata name",
"label.userdatadetails": "Userdata details",
diff --git a/ui/src/config/section/infra/primaryStorages.js
b/ui/src/config/section/infra/primaryStorages.js
index c4932b2daad..826ffc7422b 100644
--- a/ui/src/config/section/infra/primaryStorages.js
+++ b/ui/src/config/section/infra/primaryStorages.js
@@ -35,7 +35,7 @@ export default {
fields.push('zonename')
return fields
},
- details: ['name', 'id', 'ipaddress', 'type', 'nfsmountopts', 'scope',
'tags', 'path', 'provider', 'hypervisor', 'overprovisionfactor',
'disksizetotal', 'disksizeallocated', 'disksizeused', 'clustername', 'podname',
'zonename', 'created'],
+ details: ['name', 'id', 'ipaddress', 'type', 'nfsmountopts', 'scope',
'tags', 'path', 'provider', 'hypervisor', 'overprovisionfactor',
'disksizetotal', 'disksizeallocated', 'disksizeused', 'capacityiops',
'usediops', 'clustername', 'podname', 'zonename', 'created'],
related: [{
name: 'volume',
title: 'label.volumes',