mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
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 <abhishek.mrt22@gmail.com>
This commit is contained in:
parent
9bc283e5c2
commit
bd488c4bba
@ -26,4 +26,7 @@ public interface StorageStats {
|
||||
* @return bytes capacity of the storage server
|
||||
*/
|
||||
public long getCapacityBytes();
|
||||
|
||||
Long getCapacityIops();
|
||||
Long getUsedIops();
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long capacity, long used) {
|
||||
@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.capacity = capacity;
|
||||
this.used = used;
|
||||
this.capacityBytes = capacityBytes;
|
||||
this.usedBytes = usedBytes;
|
||||
}
|
||||
|
||||
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, long capacityBytes, long usedBytes, Long capacityIops, Long usedIops) {
|
||||
super(cmd, true, null);
|
||||
this.capacityBytes = capacityBytes;
|
||||
this.usedBytes = usedBytes;
|
||||
this.capacityIops = capacityIops;
|
||||
this.usedIops = usedIops;
|
||||
}
|
||||
|
||||
public GetStorageStatsAnswer(GetStorageStatsCommand cmd, String details) {
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
2
debian/control
vendored
2
debian/control
vendored
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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" ');
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 +
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -31,4 +31,6 @@ public interface MockVolumeDao extends GenericDao<MockVolumeVO, Long> {
|
||||
public MockVolumeVO findByName(String volumeName);
|
||||
|
||||
Long findTotalStorageId(long id);
|
||||
|
||||
int countForStorageId(long id);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user