mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Introduce volume allocation algorithm global configuration (#10696)
This commit is contained in:
parent
7bab40db6f
commit
d5ba23c848
@ -62,7 +62,7 @@ public interface DeploymentClusterPlanner extends DeploymentPlanner {
|
|||||||
"vm.allocation.algorithm",
|
"vm.allocation.algorithm",
|
||||||
"Advanced",
|
"Advanced",
|
||||||
"random",
|
"random",
|
||||||
"Order in which hosts within a cluster will be considered for VM/volume allocation. The value can be 'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', or 'firstfitleastconsumed'.",
|
"Order in which hosts within a cluster will be considered for VM allocation. The value can be 'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', or 'firstfitleastconsumed'.",
|
||||||
true,
|
true,
|
||||||
ConfigKey.Scope.Global, null, null, null, null, null,
|
ConfigKey.Scope.Global, null, null, null, null, null,
|
||||||
ConfigKey.Kind.Select,
|
ConfigKey.Kind.Select,
|
||||||
|
|||||||
@ -84,6 +84,17 @@ public interface VolumeOrchestrationService {
|
|||||||
"The maximum size for a volume (in GiB).",
|
"The maximum size for a volume (in GiB).",
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
ConfigKey<String> VolumeAllocationAlgorithm = new ConfigKey<>(
|
||||||
|
String.class,
|
||||||
|
"volume.allocation.algorithm",
|
||||||
|
"Advanced",
|
||||||
|
"random",
|
||||||
|
"Order in which storage pool within a cluster will be considered for volume allocation. The value can be 'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', or 'firstfitleastconsumed'.",
|
||||||
|
true,
|
||||||
|
ConfigKey.Scope.Global, null, null, null, null, null,
|
||||||
|
ConfigKey.Kind.Select,
|
||||||
|
"random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed");
|
||||||
|
|
||||||
VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType)
|
VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType)
|
||||||
throws ConcurrentOperationException, StorageUnavailableException;
|
throws ConcurrentOperationException, StorageUnavailableException;
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import java.util.stream.Collectors;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
|
import com.cloud.deploy.DeploymentClusterPlanner;
|
||||||
import com.cloud.exception.ResourceAllocationException;
|
import com.cloud.exception.ResourceAllocationException;
|
||||||
import com.cloud.storage.DiskOfferingVO;
|
import com.cloud.storage.DiskOfferingVO;
|
||||||
import com.cloud.storage.VMTemplateVO;
|
import com.cloud.storage.VMTemplateVO;
|
||||||
@ -74,6 +75,7 @@ import org.apache.cloudstack.framework.async.AsyncCallFuture;
|
|||||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
import org.apache.cloudstack.framework.config.Configurable;
|
import org.apache.cloudstack.framework.config.Configurable;
|
||||||
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||||
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
|
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
|
||||||
import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
|
import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
|
||||||
@ -262,6 +264,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||||||
StoragePoolHostDao storagePoolHostDao;
|
StoragePoolHostDao storagePoolHostDao;
|
||||||
@Inject
|
@Inject
|
||||||
DiskOfferingDao diskOfferingDao;
|
DiskOfferingDao diskOfferingDao;
|
||||||
|
@Inject
|
||||||
|
ConfigDepot configDepot;
|
||||||
|
@Inject
|
||||||
|
ConfigurationDao configurationDao;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected SnapshotHelper snapshotHelper;
|
protected SnapshotHelper snapshotHelper;
|
||||||
@ -2047,7 +2053,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConfigKey<?>[] getConfigKeys() {
|
public ConfigKey<?>[] getConfigKeys() {
|
||||||
return new ConfigKey<?>[] {RecreatableSystemVmEnabled, MaxVolumeSize, StorageHAMigrationEnabled, StorageMigrationEnabled, CustomDiskOfferingMaxSize, CustomDiskOfferingMinSize, VolumeUrlCheck};
|
return new ConfigKey<?>[] {
|
||||||
|
RecreatableSystemVmEnabled, MaxVolumeSize, StorageHAMigrationEnabled, StorageMigrationEnabled,
|
||||||
|
CustomDiskOfferingMaxSize, CustomDiskOfferingMinSize, VolumeUrlCheck, VolumeAllocationAlgorithm};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -2060,6 +2068,18 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean start() {
|
||||||
|
if (configDepot.isNewConfig(VolumeAllocationAlgorithm)) {
|
||||||
|
String vmAllocationAlgo = DeploymentClusterPlanner.VmAllocationAlgorithm.value();
|
||||||
|
if (com.cloud.utils.StringUtils.isNotEmpty(vmAllocationAlgo) && !VolumeAllocationAlgorithm.defaultValue().equalsIgnoreCase(vmAllocationAlgo)) {
|
||||||
|
logger.debug("Updating value for configuration: {} to {}", VolumeAllocationAlgorithm.key(), vmAllocationAlgo);
|
||||||
|
configurationDao.update(VolumeAllocationAlgorithm.key(), vmAllocationAlgo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void cleanupVolumeDuringAttachFailure(Long volumeId, Long vmId) {
|
private void cleanupVolumeDuringAttachFailure(Long volumeId, Long vmId) {
|
||||||
VolumeVO volume = _volsDao.findById(volumeId);
|
VolumeVO volume = _volsDao.findById(volumeId);
|
||||||
if (volume == null) {
|
if (volume == null) {
|
||||||
|
|||||||
@ -20,14 +20,29 @@ import java.util.ArrayList;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import com.cloud.configuration.Resource;
|
||||||
|
import com.cloud.deploy.DeploymentClusterPlanner;
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.exception.StorageAccessException;
|
||||||
|
import com.cloud.host.Host;
|
||||||
|
import com.cloud.host.HostVO;
|
||||||
import com.cloud.hypervisor.Hypervisor;
|
import com.cloud.hypervisor.Hypervisor;
|
||||||
import com.cloud.offering.DiskOffering;
|
import com.cloud.offering.DiskOffering;
|
||||||
|
import com.cloud.storage.ScopeType;
|
||||||
import com.cloud.storage.DataStoreRole;
|
import com.cloud.storage.DataStoreRole;
|
||||||
import com.cloud.storage.Storage;
|
import com.cloud.storage.Storage;
|
||||||
import com.cloud.storage.Volume;
|
import com.cloud.storage.Volume;
|
||||||
|
import com.cloud.storage.Volume.Type;
|
||||||
|
import com.cloud.storage.VolumeVO;
|
||||||
import com.cloud.storage.dao.VolumeDao;
|
import com.cloud.storage.dao.VolumeDao;
|
||||||
import com.cloud.user.Account;
|
import com.cloud.user.Account;
|
||||||
|
import com.cloud.user.ResourceLimitService;
|
||||||
|
import com.cloud.uservm.UserVm;
|
||||||
|
import com.cloud.utils.db.EntityManager;
|
||||||
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
import com.cloud.utils.Pair;
|
import com.cloud.utils.Pair;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||||
@ -40,6 +55,13 @@ import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
|
|||||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
||||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
|
||||||
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
|
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
|
import org.apache.cloudstack.secret.PassphraseVO;
|
||||||
|
import org.apache.cloudstack.secret.dao.PassphraseDao;
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -53,14 +75,9 @@ import org.mockito.Spy;
|
|||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import com.cloud.configuration.Resource;
|
import static org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService.VolumeAllocationAlgorithm;
|
||||||
import com.cloud.exception.StorageAccessException;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import com.cloud.host.Host;
|
import static org.junit.Assert.assertTrue;
|
||||||
import com.cloud.host.HostVO;
|
|
||||||
import com.cloud.storage.VolumeVO;
|
|
||||||
import com.cloud.storage.Volume.Type;
|
|
||||||
import com.cloud.user.ResourceLimitService;
|
|
||||||
import com.cloud.utils.exception.CloudRuntimeException;
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class VolumeOrchestratorTest {
|
public class VolumeOrchestratorTest {
|
||||||
@ -73,6 +90,17 @@ public class VolumeOrchestratorTest {
|
|||||||
protected VolumeDataFactory volumeDataFactory;
|
protected VolumeDataFactory volumeDataFactory;
|
||||||
@Mock
|
@Mock
|
||||||
protected VolumeDao volumeDao;
|
protected VolumeDao volumeDao;
|
||||||
|
@Mock
|
||||||
|
protected PassphraseDao passphraseDao;
|
||||||
|
@Mock
|
||||||
|
protected PrimaryDataStoreDao storagePoolDao;
|
||||||
|
@Mock
|
||||||
|
protected EntityManager entityMgr;
|
||||||
|
@Mock
|
||||||
|
ConfigDepot configDepot;
|
||||||
|
@Mock
|
||||||
|
ConfigurationDao configurationDao;
|
||||||
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private SnapshotDataStoreDao snapshotDataStoreDaoMock;
|
private SnapshotDataStoreDao snapshotDataStoreDaoMock;
|
||||||
@ -87,6 +115,9 @@ public class VolumeOrchestratorTest {
|
|||||||
|
|
||||||
private static final Long DEFAULT_ACCOUNT_PS_RESOURCE_COUNT = 100L;
|
private static final Long DEFAULT_ACCOUNT_PS_RESOURCE_COUNT = 100L;
|
||||||
private Long accountPSResourceCount;
|
private Long accountPSResourceCount;
|
||||||
|
private static final long MOCK_VM_ID = 202L;
|
||||||
|
private static final long MOCK_POOL_ID = 303L;
|
||||||
|
private static final String MOCK_VM_NAME = "Test-VM";
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@ -224,6 +255,349 @@ public class VolumeOrchestratorTest {
|
|||||||
Mockito.verify(volume, Mockito.times(1)).setState(Volume.State.Ready);
|
Mockito.verify(volume, Mockito.times(1)).setState(Volume.State.Ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateDuplicateVolumeVOBasic() {
|
||||||
|
Volume oldVol = Mockito.mock(Volume.class);
|
||||||
|
Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT);
|
||||||
|
Mockito.when(oldVol.getName()).thenReturn("testVol");
|
||||||
|
Mockito.when(oldVol.getDataCenterId()).thenReturn(1L);
|
||||||
|
Mockito.when(oldVol.getDomainId()).thenReturn(2L);
|
||||||
|
Mockito.when(oldVol.getAccountId()).thenReturn(3L);
|
||||||
|
Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L);
|
||||||
|
Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.THIN);
|
||||||
|
Mockito.when(oldVol.getSize()).thenReturn(10L);
|
||||||
|
Mockito.when(oldVol.getMinIops()).thenReturn(100L);
|
||||||
|
Mockito.when(oldVol.getMaxIops()).thenReturn(200L);
|
||||||
|
Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.test");
|
||||||
|
Mockito.when(oldVol.getTemplateId()).thenReturn(5L);
|
||||||
|
Mockito.when(oldVol.getDeviceId()).thenReturn(1L);
|
||||||
|
Mockito.when(oldVol.getInstanceId()).thenReturn(6L);
|
||||||
|
Mockito.when(oldVol.isRecreatable()).thenReturn(false);
|
||||||
|
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
|
||||||
|
Mockito.when(oldVol.getPassphraseId()).thenReturn(null); // no encryption
|
||||||
|
|
||||||
|
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volumeDao.persist(Mockito.any(VolumeVO.class))).thenReturn(persistedVol);
|
||||||
|
|
||||||
|
VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, null);
|
||||||
|
assertNotNull(result);
|
||||||
|
Mockito.verify(volumeDao, Mockito.times(1)).persist(Mockito.any(VolumeVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateDuplicateVolumeVOWithEncryption() {
|
||||||
|
Volume oldVol = Mockito.mock(Volume.class);
|
||||||
|
Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT);
|
||||||
|
Mockito.when(oldVol.getName()).thenReturn("secureVol");
|
||||||
|
Mockito.when(oldVol.getDataCenterId()).thenReturn(1L);
|
||||||
|
Mockito.when(oldVol.getDomainId()).thenReturn(2L);
|
||||||
|
Mockito.when(oldVol.getAccountId()).thenReturn(3L);
|
||||||
|
Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L);
|
||||||
|
Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.THIN);
|
||||||
|
Mockito.when(oldVol.getSize()).thenReturn(10L);
|
||||||
|
Mockito.when(oldVol.getMinIops()).thenReturn(100L);
|
||||||
|
Mockito.when(oldVol.getMaxIops()).thenReturn(200L);
|
||||||
|
Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.secure");
|
||||||
|
Mockito.when(oldVol.getTemplateId()).thenReturn(5L);
|
||||||
|
Mockito.when(oldVol.getDeviceId()).thenReturn(2L);
|
||||||
|
Mockito.when(oldVol.getInstanceId()).thenReturn(7L);
|
||||||
|
Mockito.when(oldVol.isRecreatable()).thenReturn(true);
|
||||||
|
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.RAW);
|
||||||
|
Mockito.when(oldVol.getPassphraseId()).thenReturn(42L);
|
||||||
|
|
||||||
|
PassphraseVO passphrase = Mockito.mock(PassphraseVO.class);
|
||||||
|
Mockito.when(passphrase.getId()).thenReturn(999L);
|
||||||
|
Mockito.when(passphraseDao.persist(Mockito.any())).thenReturn(passphrase);
|
||||||
|
|
||||||
|
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol);
|
||||||
|
|
||||||
|
VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, null);
|
||||||
|
assertNotNull(result);
|
||||||
|
Mockito.verify(passphraseDao).persist(Mockito.any(PassphraseVO.class));
|
||||||
|
Mockito.verify(volumeDao).persist(Mockito.any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateDuplicateVolumeVOWithTemplateOverride() {
|
||||||
|
Volume oldVol = Mockito.mock(Volume.class);
|
||||||
|
Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT);
|
||||||
|
Mockito.when(oldVol.getName()).thenReturn("tmplVol");
|
||||||
|
Mockito.when(oldVol.getDataCenterId()).thenReturn(1L);
|
||||||
|
Mockito.when(oldVol.getDomainId()).thenReturn(2L);
|
||||||
|
Mockito.when(oldVol.getAccountId()).thenReturn(3L);
|
||||||
|
Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L);
|
||||||
|
Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.THIN);
|
||||||
|
Mockito.when(oldVol.getSize()).thenReturn(20L);
|
||||||
|
Mockito.when(oldVol.getMinIops()).thenReturn(50L);
|
||||||
|
Mockito.when(oldVol.getMaxIops()).thenReturn(250L);
|
||||||
|
Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.tmpl");
|
||||||
|
|
||||||
|
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol);
|
||||||
|
|
||||||
|
PassphraseVO mockPassPhrase = Mockito.mock(PassphraseVO.class);
|
||||||
|
Mockito.when(passphraseDao.persist(Mockito.any())).thenReturn(mockPassPhrase);
|
||||||
|
|
||||||
|
VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, 222L);
|
||||||
|
assertNotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateDuplicateVolumeVOEncryptionFromOldVolumeOnly() {
|
||||||
|
Volume oldVol = Mockito.mock(Volume.class);
|
||||||
|
Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT);
|
||||||
|
Mockito.when(oldVol.getName()).thenReturn("vol-old");
|
||||||
|
Mockito.when(oldVol.getDataCenterId()).thenReturn(1L);
|
||||||
|
Mockito.when(oldVol.getDomainId()).thenReturn(2L);
|
||||||
|
Mockito.when(oldVol.getAccountId()).thenReturn(3L);
|
||||||
|
Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L);
|
||||||
|
Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.SPARSE);
|
||||||
|
Mockito.when(oldVol.getSize()).thenReturn(30L);
|
||||||
|
Mockito.when(oldVol.getMinIops()).thenReturn(10L);
|
||||||
|
Mockito.when(oldVol.getMaxIops()).thenReturn(500L);
|
||||||
|
Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.old");
|
||||||
|
Mockito.when(oldVol.getTemplateId()).thenReturn(123L);
|
||||||
|
Mockito.when(oldVol.getDeviceId()).thenReturn(1L);
|
||||||
|
Mockito.when(oldVol.getInstanceId()).thenReturn(100L);
|
||||||
|
Mockito.when(oldVol.isRecreatable()).thenReturn(false);
|
||||||
|
Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.RAW);
|
||||||
|
|
||||||
|
DiskOffering diskOffering = Mockito.mock(DiskOffering.class);
|
||||||
|
Mockito.when(diskOffering.getEncrypt()).thenReturn(false); // explicitly disables encryption
|
||||||
|
|
||||||
|
VolumeVO persistedVol = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol);
|
||||||
|
|
||||||
|
VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, diskOffering, null);
|
||||||
|
assertNotNull(result);
|
||||||
|
Mockito.verify(volumeDao).persist(Mockito.any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeOnSharedStoragePoolTrue() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getPoolId()).thenReturn(MOCK_POOL_ID);
|
||||||
|
|
||||||
|
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Mockito.when(pool.getScope()).thenReturn(ScopeType.CLUSTER); // Shared scope
|
||||||
|
Mockito.when(storagePoolDao.findById(MOCK_POOL_ID)).thenReturn(pool);
|
||||||
|
|
||||||
|
assertTrue(volumeOrchestrator.volumeOnSharedStoragePool(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeOnSharedStoragePoolFalseHostScope() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getPoolId()).thenReturn(MOCK_POOL_ID);
|
||||||
|
|
||||||
|
StoragePoolVO pool = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Mockito.when(pool.getScope()).thenReturn(ScopeType.HOST); // Local scope
|
||||||
|
Mockito.when(storagePoolDao.findById(MOCK_POOL_ID)).thenReturn(pool);
|
||||||
|
|
||||||
|
Assert.assertFalse(volumeOrchestrator.volumeOnSharedStoragePool(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeOnSharedStoragePoolFalseNoPool() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getPoolId()).thenReturn(null); // No pool associated
|
||||||
|
|
||||||
|
Assert.assertFalse(volumeOrchestrator.volumeOnSharedStoragePool(volume));
|
||||||
|
Mockito.verify(storagePoolDao, Mockito.never()).findById(Mockito.anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeOnSharedStoragePoolFalsePoolNotFound() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getPoolId()).thenReturn(MOCK_POOL_ID);
|
||||||
|
|
||||||
|
Mockito.when(storagePoolDao.findById(MOCK_POOL_ID)).thenReturn(null); // Pool not found in DB
|
||||||
|
|
||||||
|
Assert.assertFalse(volumeOrchestrator.volumeOnSharedStoragePool(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeInactiveNoVmId() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(null);
|
||||||
|
assertTrue(volumeOrchestrator.volumeInactive(volume));
|
||||||
|
Mockito.verify(entityMgr, Mockito.never()).findById(Mockito.eq(UserVm.class), Mockito.anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeInactiveVmNotFound() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID);
|
||||||
|
Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(null);
|
||||||
|
assertTrue(volumeOrchestrator.volumeInactive(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeInactiveVmStopped() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID);
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
|
||||||
|
Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(vm);
|
||||||
|
assertTrue(volumeOrchestrator.volumeInactive(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeInactiveVmDestroyed() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID);
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Destroyed);
|
||||||
|
Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(vm);
|
||||||
|
assertTrue(volumeOrchestrator.volumeInactive(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVolumeInactiveVmRunning() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID);
|
||||||
|
UserVm vm = Mockito.mock(UserVm.class);
|
||||||
|
Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Running); // Active state
|
||||||
|
Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(vm);
|
||||||
|
Assert.assertFalse(volumeOrchestrator.volumeInactive(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetVmNameOnVolumeNoVmId() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(null);
|
||||||
|
Assert.assertNull(volumeOrchestrator.getVmNameOnVolume(volume));
|
||||||
|
Mockito.verify(entityMgr, Mockito.never()).findById(Mockito.eq(VirtualMachine.class), Mockito.anyLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetVmNameOnVolumeVmNotFound() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID);
|
||||||
|
Mockito.when(entityMgr.findById(VirtualMachine.class, MOCK_VM_ID)).thenReturn(null);
|
||||||
|
Assert.assertNull(volumeOrchestrator.getVmNameOnVolume(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetVmNameOnVolumeSuccess() {
|
||||||
|
VolumeVO volume = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID);
|
||||||
|
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||||
|
Mockito.when(vm.getInstanceName()).thenReturn(MOCK_VM_NAME);
|
||||||
|
Mockito.when(entityMgr.findById(VirtualMachine.class, MOCK_VM_ID)).thenReturn(vm);
|
||||||
|
Assert.assertEquals(MOCK_VM_NAME, volumeOrchestrator.getVmNameOnVolume(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateVolumeSizeRangeValid() throws Exception {
|
||||||
|
overrideDefaultConfigValue(VolumeOrchestrator.MaxVolumeSize, "2000");
|
||||||
|
assertTrue(volumeOrchestrator.validateVolumeSizeRange(1024 * 1024 * 1024)); // 1 GiB
|
||||||
|
assertTrue(volumeOrchestrator.validateVolumeSizeRange(2000 * 1024 * 1024 * 1024)); // 2 TiB
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidParameterValueException.class)
|
||||||
|
public void testValidateVolumeSizeRangeTooSmall() {
|
||||||
|
volumeOrchestrator.validateVolumeSizeRange(1024L); // Less than 1GiB
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidParameterValueException.class)
|
||||||
|
public void testValidateVolumeSizeRangeNegative() {
|
||||||
|
volumeOrchestrator.validateVolumeSizeRange(-10); // Negative size
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = InvalidParameterValueException.class)
|
||||||
|
public void testValidateVolumeSizeRangeTooLarge() throws Exception {
|
||||||
|
overrideDefaultConfigValue(VolumeOrchestrator.MaxVolumeSize, "100L");
|
||||||
|
volumeOrchestrator.validateVolumeSizeRange(101);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCanVmRestartOnAnotherServerAllShared() {
|
||||||
|
VolumeVO vol1 = Mockito.mock(VolumeVO.class);
|
||||||
|
VolumeVO vol2 = Mockito.mock(VolumeVO.class);
|
||||||
|
Mockito.when(vol1.getPoolId()).thenReturn(10L);
|
||||||
|
Mockito.when(vol2.getPoolId()).thenReturn(20L);
|
||||||
|
Mockito.when(vol1.isRecreatable()).thenReturn(false);
|
||||||
|
Mockito.when(vol2.isRecreatable()).thenReturn(false);
|
||||||
|
|
||||||
|
|
||||||
|
StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class);
|
||||||
|
StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Mockito.when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); // Shared
|
||||||
|
Mockito.when(pool2.getPoolType()).thenReturn(Storage.StoragePoolType.RBD); // Shared
|
||||||
|
|
||||||
|
Mockito.when(volumeDao.findCreatedByInstance(MOCK_VM_ID)).thenReturn(List.of(vol1, vol2));
|
||||||
|
Mockito.when(storagePoolDao.findById(10L)).thenReturn(pool1);
|
||||||
|
Mockito.when(storagePoolDao.findById(20L)).thenReturn(pool2);
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(volumeOrchestrator.canVmRestartOnAnotherServer(MOCK_VM_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCanVmRestartOnAnotherServerOneLocalNotRecreatable() {
|
||||||
|
VolumeVO vol1 = Mockito.mock(VolumeVO.class);
|
||||||
|
VolumeVO vol2 = Mockito.mock(VolumeVO.class); // Local, not recreatable
|
||||||
|
Mockito.when(vol1.getPoolId()).thenReturn(10L);
|
||||||
|
Mockito.when(vol2.getPoolId()).thenReturn(30L);
|
||||||
|
Mockito.when(vol1.isRecreatable()).thenReturn(false);
|
||||||
|
Mockito.when(vol2.isRecreatable()).thenReturn(false); // Not recreatable
|
||||||
|
|
||||||
|
StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class);
|
||||||
|
StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Mockito.when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); // Shared
|
||||||
|
Mockito.when(pool2.getPoolType()).thenReturn(Storage.StoragePoolType.LVM); // Local
|
||||||
|
|
||||||
|
Mockito.when(volumeDao.findCreatedByInstance(MOCK_VM_ID)).thenReturn(List.of(vol1, vol2));
|
||||||
|
Mockito.when(storagePoolDao.findById(10L)).thenReturn(pool1);
|
||||||
|
Mockito.when(storagePoolDao.findById(30L)).thenReturn(pool2);
|
||||||
|
|
||||||
|
Assert.assertFalse("VM restart should be false if a non-recreatable local disk exists",
|
||||||
|
volumeOrchestrator.canVmRestartOnAnotherServer(MOCK_VM_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCanVmRestartOnAnotherServerOneLocalRecreatable() {
|
||||||
|
VolumeVO vol1 = Mockito.mock(VolumeVO.class);
|
||||||
|
VolumeVO vol2 = Mockito.mock(VolumeVO.class); // Local, but recreatable
|
||||||
|
Mockito.when(vol1.getPoolId()).thenReturn(10L);
|
||||||
|
Mockito.when(vol2.getPoolId()).thenReturn(30L);
|
||||||
|
Mockito.when(vol1.isRecreatable()).thenReturn(false);
|
||||||
|
Mockito.when(vol2.isRecreatable()).thenReturn(true); // Recreatable
|
||||||
|
|
||||||
|
StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class);
|
||||||
|
StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class);
|
||||||
|
Mockito.when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); // Shared
|
||||||
|
|
||||||
|
Mockito.when(volumeDao.findCreatedByInstance(MOCK_VM_ID)).thenReturn(List.of(vol1, vol2));
|
||||||
|
Mockito.when(storagePoolDao.findById(10L)).thenReturn(pool1);
|
||||||
|
Mockito.when(storagePoolDao.findById(30L)).thenReturn(pool2);
|
||||||
|
|
||||||
|
assertTrue("VM restart should be true if local disk is recreatable",
|
||||||
|
volumeOrchestrator.canVmRestartOnAnotherServer(MOCK_VM_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void overrideDefaultConfigValue(final ConfigKey configKey, final String value) throws IllegalAccessException, NoSuchFieldException {
|
||||||
|
final Field f = ConfigKey.class.getDeclaredField("_defaultValue");
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(configKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStart() throws Exception {
|
||||||
|
Mockito.when(configDepot.isNewConfig(VolumeAllocationAlgorithm)).thenReturn(true);
|
||||||
|
overrideDefaultConfigValue(DeploymentClusterPlanner.VmAllocationAlgorithm, "firstfit");
|
||||||
|
Mockito.when(configurationDao.update(Mockito.anyString(), Mockito.anyString())).thenReturn(true);
|
||||||
|
volumeOrchestrator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfigKeys() {
|
||||||
|
assertTrue(volumeOrchestrator.getConfigKeys().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getVolumeCheckpointPathsAndImageStoreUrlsTestReturnEmptyListsIfNotKVM() {
|
public void getVolumeCheckpointPathsAndImageStoreUrlsTestReturnEmptyListsIfNotKVM() {
|
||||||
Pair<List<String>, Set<String>> result = volumeOrchestrator.getVolumeCheckpointPathsAndImageStoreUrls(0, Hypervisor.HypervisorType.VMware);
|
Pair<List<String>, Set<String>> result = volumeOrchestrator.getVolumeCheckpointPathsAndImageStoreUrls(0, Hypervisor.HypervisorType.VMware);
|
||||||
|
|||||||
@ -64,5 +64,5 @@ public interface CapacityDao extends GenericDao<CapacityVO, Long> {
|
|||||||
|
|
||||||
float findClusterConsumption(Long clusterId, short capacityType, long computeRequested);
|
float findClusterConsumption(Long clusterId, short capacityType, long computeRequested);
|
||||||
|
|
||||||
List<Long> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityType);
|
Pair<List<Long>, Map<Long, Double>> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityType);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1028,10 +1028,11 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Long> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityTypeForOrdering){
|
public Pair<List<Long>, Map<Long, Double>> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityTypeForOrdering){
|
||||||
TransactionLegacy txn = TransactionLegacy.currentTxn();
|
TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||||
PreparedStatement pstmt = null;
|
PreparedStatement pstmt = null;
|
||||||
List<Long> result = new ArrayList<Long>();
|
List<Long> result = new ArrayList<>();
|
||||||
|
Map<Long, Double> hostCapacityMap = new HashMap<>();
|
||||||
StringBuilder sql = new StringBuilder(ORDER_HOSTS_BY_FREE_CAPACITY_PART1);
|
StringBuilder sql = new StringBuilder(ORDER_HOSTS_BY_FREE_CAPACITY_PART1);
|
||||||
if (zoneId != null) {
|
if (zoneId != null) {
|
||||||
sql.append(" AND data_center_id = ?");
|
sql.append(" AND data_center_id = ?");
|
||||||
@ -1054,9 +1055,11 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
|
|||||||
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
ResultSet rs = pstmt.executeQuery();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
result.add(rs.getLong(1));
|
Long hostId = rs.getLong(1);
|
||||||
|
result.add(hostId);
|
||||||
|
hostCapacityMap.put(hostId, rs.getDouble(2));
|
||||||
}
|
}
|
||||||
return result;
|
return new Pair<>(result, hostCapacityMap);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new CloudRuntimeException("DB Exception on: " + sql, e);
|
throw new CloudRuntimeException("DB Exception on: " + sql, e);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|||||||
@ -16,31 +16,44 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package com.cloud.capacity.dao;
|
package com.cloud.capacity.dao;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import com.cloud.capacity.CapacityVO;
|
||||||
import static org.junit.Assert.assertSame;
|
import com.cloud.host.Host;
|
||||||
import static org.junit.Assert.assertTrue;
|
import com.cloud.utils.Pair;
|
||||||
import static org.mockito.Mockito.any;
|
import com.cloud.utils.Ternary;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import com.cloud.utils.db.SearchBuilder;
|
||||||
import static org.mockito.Mockito.eq;
|
import com.cloud.utils.db.SearchCriteria;
|
||||||
import static org.mockito.Mockito.mock;
|
import com.cloud.utils.db.TransactionLegacy;
|
||||||
import static org.mockito.Mockito.verify;
|
import org.junit.After;
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.Spy;
|
import org.mockito.Spy;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
import com.cloud.capacity.CapacityVO;
|
import java.sql.PreparedStatement;
|
||||||
import com.cloud.utils.db.SearchBuilder;
|
import java.sql.ResultSet;
|
||||||
import com.cloud.utils.db.SearchCriteria;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class CapacityDaoImplTest {
|
public class CapacityDaoImplTest {
|
||||||
@ -48,6 +61,14 @@ public class CapacityDaoImplTest {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
CapacityDaoImpl capacityDao = new CapacityDaoImpl();
|
CapacityDaoImpl capacityDao = new CapacityDaoImpl();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TransactionLegacy txn;
|
||||||
|
@Mock
|
||||||
|
private PreparedStatement pstmt;
|
||||||
|
@Mock
|
||||||
|
private ResultSet resultSet;
|
||||||
|
private MockedStatic<TransactionLegacy> mockedTransactionLegacy;
|
||||||
|
|
||||||
private SearchBuilder<CapacityVO> searchBuilder;
|
private SearchBuilder<CapacityVO> searchBuilder;
|
||||||
private SearchCriteria<CapacityVO> searchCriteria;
|
private SearchCriteria<CapacityVO> searchCriteria;
|
||||||
|
|
||||||
@ -59,6 +80,16 @@ public class CapacityDaoImplTest {
|
|||||||
searchCriteria = mock(SearchCriteria.class);
|
searchCriteria = mock(SearchCriteria.class);
|
||||||
doReturn(searchBuilder).when(capacityDao).createSearchBuilder();
|
doReturn(searchBuilder).when(capacityDao).createSearchBuilder();
|
||||||
when(searchBuilder.create()).thenReturn(searchCriteria);
|
when(searchBuilder.create()).thenReturn(searchCriteria);
|
||||||
|
|
||||||
|
mockedTransactionLegacy = Mockito.mockStatic(TransactionLegacy.class);
|
||||||
|
mockedTransactionLegacy.when(TransactionLegacy::currentTxn).thenReturn(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
if (mockedTransactionLegacy != null) {
|
||||||
|
mockedTransactionLegacy.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -96,4 +127,207 @@ public class CapacityDaoImplTest {
|
|||||||
verify(capacityDao).listBy(searchCriteria);
|
verify(capacityDao).listBy(searchCriteria);
|
||||||
assertTrue(result.isEmpty());
|
assertTrue(result.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListClustersCrossingThresholdEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
List<Long> result = capacityDao.listClustersCrossingThreshold((short)1, 1L, "cpu.threshold", 5000L);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindCapacityByZoneAndHostTagNoResults() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
Ternary<Long, Long, Long> result = capacityDao.findCapacityByZoneAndHostTag(1L, "host-tag");
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(Long.valueOf(0L), result.first());
|
||||||
|
assertEquals(Long.valueOf(0L), result.second());
|
||||||
|
assertEquals(Long.valueOf(0L), result.third());
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void testFindByHostIdType() {
|
||||||
|
CapacityVO capacity = new CapacityVO();
|
||||||
|
capacity.setHostId(1L);
|
||||||
|
capacity.setCapacityType((short) 1);
|
||||||
|
|
||||||
|
doReturn(capacity).when(capacityDao).findOneBy(any());
|
||||||
|
|
||||||
|
CapacityVO found = capacityDao.findByHostIdType(1L, (short) 1);
|
||||||
|
assertNotNull(found);
|
||||||
|
assertEquals(Long.valueOf(1L), found.getHostOrPoolId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateAllocatedAddition() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
doNothing().when(txn).start();
|
||||||
|
when(txn.commit()).thenReturn(true);
|
||||||
|
|
||||||
|
capacityDao.updateAllocated(1L, 1000L, (short)1, true);
|
||||||
|
|
||||||
|
verify(txn, times(1)).start();
|
||||||
|
verify(txn, times(1)).commit();
|
||||||
|
verify(pstmt, times(1)).executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateAllocatedSubtraction() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
doNothing().when(txn).start();
|
||||||
|
when(txn.commit()).thenReturn(true);
|
||||||
|
|
||||||
|
capacityDao.updateAllocated(1L, 500L, (short)1, false);
|
||||||
|
|
||||||
|
verify(txn, times(1)).start();
|
||||||
|
verify(txn, times(1)).commit();
|
||||||
|
verify(pstmt, times(1)).executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindFilteredCapacityByEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
List<CapacityDaoImpl.SummedCapacity> result = capacityDao.findFilteredCapacityBy(null, null, null, null, Collections.emptyList(), Collections.emptyList());
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListClustersInZoneOrPodByHostCapacitiesEmpty() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
List<Long> resultZone = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, (short)0, true);
|
||||||
|
assertNotNull(resultZone);
|
||||||
|
assertTrue(resultZone.isEmpty());
|
||||||
|
|
||||||
|
List<Long> resultPod = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, (short)0, false);
|
||||||
|
assertNotNull(resultPod);
|
||||||
|
assertTrue(resultPod.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListHostsWithEnoughCapacityEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
List<Long> result = capacityDao.listHostsWithEnoughCapacity(1, 100L, 200L, Host.Type.Routing.toString());
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOrderClustersByAggregateCapacityEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
Pair<List<Long>, Map<Long, Double>> result = capacityDao.orderClustersByAggregateCapacity(1L, 1L, (short) 1, true);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.first().isEmpty());
|
||||||
|
assertTrue(result.second().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOrderPodsByAggregateCapacityEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
Pair<List<Long>, Map<Long, Double>> result = capacityDao.orderPodsByAggregateCapacity(1L, (short) 1);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.first().isEmpty());
|
||||||
|
assertTrue(result.second().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateCapacityState() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeUpdate()).thenReturn(1);
|
||||||
|
|
||||||
|
capacityDao.updateCapacityState(1L, 1L, 1L, 1L, "Enabled", new short[]{1});
|
||||||
|
|
||||||
|
verify(pstmt, times(1)).executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindClusterConsumption() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(true);
|
||||||
|
when(resultSet.getFloat(1)).thenReturn(0.5f);
|
||||||
|
|
||||||
|
float result = capacityDao.findClusterConsumption(1L, (short) 1, 1000L);
|
||||||
|
assertEquals(0.5f, result, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListPodsByHostCapacitiesEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
List<Long> result = capacityDao.listPodsByHostCapacities(1L, 2, 1024L, (short)0);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOrderHostsByFreeCapacityEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
Pair<List<Long>, Map<Long, Double>> result = capacityDao.orderHostsByFreeCapacity(1L, 1L, (short) 0);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.first().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindByClusterPodZoneEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
List<CapacityDaoImpl.SummedCapacity> result = capacityDao.findByClusterPodZone(1L, 1L, 1L);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListCapacitiesGroupedByLevelAndTypeEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
List<CapacityDaoImpl.SummedCapacity> result = capacityDao.listCapacitiesGroupedByLevelAndType(0, 1L,
|
||||||
|
1L, 1L, 0, Collections.emptyList(), Collections.emptyList(), 1L);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindCapacityByEmptyResult() throws Exception {
|
||||||
|
when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt);
|
||||||
|
when(pstmt.executeQuery()).thenReturn(resultSet);
|
||||||
|
when(resultSet.next()).thenReturn(false);
|
||||||
|
|
||||||
|
List<CapacityDaoImpl.SummedCapacity> result = capacityDao.findCapacityBy(1, 1L, 1L, 1L);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,64 +16,68 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package org.apache.cloudstack.storage.allocator;
|
package org.apache.cloudstack.storage.allocator;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.naming.ConfigurationException;
|
|
||||||
|
|
||||||
import com.cloud.api.query.dao.StoragePoolJoinDao;
|
import com.cloud.api.query.dao.StoragePoolJoinDao;
|
||||||
import com.cloud.exception.StorageUnavailableException;
|
|
||||||
import com.cloud.storage.ScopeType;
|
|
||||||
import com.cloud.storage.StoragePoolStatus;
|
|
||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
|
||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
|
||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
|
||||||
|
|
||||||
import com.cloud.utils.Pair;
|
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
|
|
||||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
|
||||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
|
||||||
|
|
||||||
import com.cloud.capacity.Capacity;
|
import com.cloud.capacity.Capacity;
|
||||||
import com.cloud.capacity.dao.CapacityDao;
|
import com.cloud.capacity.dao.CapacityDao;
|
||||||
import com.cloud.dc.ClusterVO;
|
import com.cloud.dc.ClusterVO;
|
||||||
import com.cloud.dc.dao.ClusterDao;
|
import com.cloud.dc.dao.ClusterDao;
|
||||||
import com.cloud.deploy.DeploymentPlan;
|
import com.cloud.deploy.DeploymentPlan;
|
||||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||||
|
import com.cloud.exception.StorageUnavailableException;
|
||||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||||
|
import com.cloud.storage.ScopeType;
|
||||||
import com.cloud.storage.Storage;
|
import com.cloud.storage.Storage;
|
||||||
import com.cloud.storage.StorageManager;
|
import com.cloud.storage.StorageManager;
|
||||||
import com.cloud.storage.StoragePool;
|
import com.cloud.storage.StoragePool;
|
||||||
|
import com.cloud.storage.StoragePoolStatus;
|
||||||
import com.cloud.storage.StorageUtil;
|
import com.cloud.storage.StorageUtil;
|
||||||
import com.cloud.storage.Volume;
|
import com.cloud.storage.Volume;
|
||||||
import com.cloud.storage.dao.VolumeDao;
|
import com.cloud.storage.dao.VolumeDao;
|
||||||
import com.cloud.user.Account;
|
import com.cloud.user.Account;
|
||||||
import com.cloud.utils.NumbersUtil;
|
import com.cloud.utils.NumbersUtil;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import com.cloud.utils.StringUtils;
|
||||||
import com.cloud.utils.component.AdapterBase;
|
import com.cloud.utils.component.AdapterBase;
|
||||||
import com.cloud.vm.DiskProfile;
|
import com.cloud.vm.DiskProfile;
|
||||||
import com.cloud.vm.VirtualMachineProfile;
|
import com.cloud.vm.VirtualMachineProfile;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||||
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||||
|
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
|
||||||
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
|
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||||
|
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.naming.ConfigurationException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public abstract class AbstractStoragePoolAllocator extends AdapterBase implements StoragePoolAllocator {
|
public abstract class AbstractStoragePoolAllocator extends AdapterBase implements StoragePoolAllocator {
|
||||||
|
|
||||||
protected BigDecimal storageOverprovisioningFactor = new BigDecimal(1);
|
protected BigDecimal storageOverprovisioningFactor = new BigDecimal(1);
|
||||||
protected String allocationAlgorithm = "random";
|
|
||||||
protected long extraBytesPerVolume = 0;
|
protected long extraBytesPerVolume = 0;
|
||||||
|
static DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||||
@Inject protected DataStoreManager dataStoreMgr;
|
@Inject protected DataStoreManager dataStoreMgr;
|
||||||
@Inject protected PrimaryDataStoreDao storagePoolDao;
|
@Inject protected PrimaryDataStoreDao storagePoolDao;
|
||||||
@Inject protected VolumeDao volumeDao;
|
@Inject protected VolumeDao volumeDao;
|
||||||
@Inject protected ConfigurationDao configDao;
|
@Inject protected ConfigurationDao configDao;
|
||||||
@Inject private CapacityDao capacityDao;
|
@Inject protected CapacityDao capacityDao;
|
||||||
@Inject private ClusterDao clusterDao;
|
@Inject private ClusterDao clusterDao;
|
||||||
@Inject private StorageManager storageMgr;
|
@Inject private StorageManager storageMgr;
|
||||||
@Inject private StorageUtil storageUtil;
|
@Inject private StorageUtil storageUtil;
|
||||||
@ -95,10 +99,6 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
|||||||
String globalStorageOverprovisioningFactor = configs.get("storage.overprovisioning.factor");
|
String globalStorageOverprovisioningFactor = configs.get("storage.overprovisioning.factor");
|
||||||
storageOverprovisioningFactor = new BigDecimal(NumbersUtil.parseFloat(globalStorageOverprovisioningFactor, 2.0f));
|
storageOverprovisioningFactor = new BigDecimal(NumbersUtil.parseFloat(globalStorageOverprovisioningFactor, 2.0f));
|
||||||
extraBytesPerVolume = 0;
|
extraBytesPerVolume = 0;
|
||||||
String allocationAlgorithm = configs.get("vm.allocation.algorithm");
|
|
||||||
if (allocationAlgorithm != null) {
|
|
||||||
this.allocationAlgorithm = allocationAlgorithm;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -142,12 +142,16 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
|||||||
capacityType, storagePool.getName(), storagePool.getUuid(), storageType
|
capacityType, storagePool.getName(), storagePool.getUuid(), storageType
|
||||||
));
|
));
|
||||||
|
|
||||||
List<Long> poolIdsByCapacity = capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType);
|
Pair<List<Long>, Map<Long, Double>> result = capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType);
|
||||||
|
List<Long> poolIdsByCapacity = result.first();
|
||||||
|
Map<Long, String> sortedHostByCapacity = result.second().entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, entry -> decimalFormat.format(entry.getValue() * 100) + "%", (e1, e2) -> e1, LinkedHashMap::new));
|
||||||
|
logger.debug("List of pools in descending order of hostId: [{}] available capacity (percentage): {}",
|
||||||
|
poolIdsByCapacity, sortedHostByCapacity);
|
||||||
|
|
||||||
logger.debug(String.format("List of pools in descending order of available capacity [%s].", poolIdsByCapacity));
|
// now filter the given list of Pools by this ordered list
|
||||||
|
|
||||||
|
|
||||||
//now filter the given list of Pools by this ordered list
|
|
||||||
Map<Long, StoragePool> poolMap = new HashMap<>();
|
Map<Long, StoragePool> poolMap = new HashMap<>();
|
||||||
for (StoragePool pool : pools) {
|
for (StoragePool pool : pools) {
|
||||||
poolMap.put(pool.getId(), pool);
|
poolMap.put(pool.getId(), pool);
|
||||||
@ -227,16 +231,16 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<StoragePool> reorderStoragePoolsBasedOnAlgorithm(List<StoragePool> pools, DeploymentPlan plan, Account account) {
|
List<StoragePool> reorderStoragePoolsBasedOnAlgorithm(List<StoragePool> pools, DeploymentPlan plan, Account account) {
|
||||||
logger.debug(String.format("Using allocation algorithm [%s] to reorder pools.", allocationAlgorithm));
|
String volumeAllocationAlgorithm = VolumeOrchestrationService.VolumeAllocationAlgorithm.value();
|
||||||
|
logger.debug("Using volume allocation algorithm {} to reorder pools.", volumeAllocationAlgorithm);
|
||||||
if (allocationAlgorithm.equals("random") || allocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
|
if (volumeAllocationAlgorithm.equals("random") || volumeAllocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
|
||||||
reorderRandomPools(pools);
|
reorderRandomPools(pools);
|
||||||
} else if (StringUtils.equalsAny(allocationAlgorithm, "userdispersing", "firstfitleastconsumed")) {
|
} else if (StringUtils.equalsAny(volumeAllocationAlgorithm, "userdispersing", "firstfitleastconsumed")) {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace(String.format("Using reordering algorithm [%s]", allocationAlgorithm));
|
logger.trace("Using reordering algorithm {}", volumeAllocationAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allocationAlgorithm.equals("userdispersing")) {
|
if (volumeAllocationAlgorithm.equals("userdispersing")) {
|
||||||
pools = reorderPoolsByNumberOfVolumes(plan, pools, account);
|
pools = reorderPoolsByNumberOfVolumes(plan, pools, account);
|
||||||
} else {
|
} else {
|
||||||
pools = reorderPoolsByCapacity(plan, pools);
|
pools = reorderPoolsByCapacity(plan, pools);
|
||||||
@ -248,7 +252,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
|
|||||||
void reorderRandomPools(List<StoragePool> pools) {
|
void reorderRandomPools(List<StoragePool> pools) {
|
||||||
StorageUtil.traceLogStoragePools(pools, logger, "pools to choose from: ");
|
StorageUtil.traceLogStoragePools(pools, logger, "pools to choose from: ");
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace(String.format("Shuffle this so that we don't check the pools in the same order. Algorithm == '%s' (or no account?)", allocationAlgorithm));
|
logger.trace("Shuffle this so that we don't check the pools in the same order. Algorithm == 'random' (or no account?)");
|
||||||
}
|
}
|
||||||
StorageUtil.traceLogStoragePools(pools, logger, "pools to shuffle: ");
|
StorageUtil.traceLogStoragePools(pools, logger, "pools to shuffle: ");
|
||||||
Collections.shuffle(pools, secureRandom);
|
Collections.shuffle(pools, secureRandom);
|
||||||
|
|||||||
@ -16,27 +16,27 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package org.apache.cloudstack.storage.allocator;
|
package org.apache.cloudstack.storage.allocator;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.naming.ConfigurationException;
|
|
||||||
|
|
||||||
import com.cloud.storage.VolumeApiServiceImpl;
|
|
||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.cloud.deploy.DeploymentPlan;
|
import com.cloud.deploy.DeploymentPlan;
|
||||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||||
import com.cloud.offering.ServiceOffering;
|
import com.cloud.offering.ServiceOffering;
|
||||||
import com.cloud.storage.ScopeType;
|
import com.cloud.storage.ScopeType;
|
||||||
import com.cloud.storage.StoragePool;
|
import com.cloud.storage.StoragePool;
|
||||||
|
import com.cloud.storage.VolumeApiServiceImpl;
|
||||||
import com.cloud.storage.dao.DiskOfferingDao;
|
import com.cloud.storage.dao.DiskOfferingDao;
|
||||||
import com.cloud.vm.DiskProfile;
|
import com.cloud.vm.DiskProfile;
|
||||||
import com.cloud.vm.VirtualMachineProfile;
|
import com.cloud.vm.VirtualMachineProfile;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.naming.ConfigurationException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
||||||
|
|
||||||
@ -116,14 +116,6 @@ public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocat
|
|||||||
@Override
|
@Override
|
||||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||||
super.configure(name, params);
|
super.configure(name, params);
|
||||||
|
|
||||||
if (configDao != null) {
|
|
||||||
Map<String, String> configs = configDao.getConfiguration(params);
|
|
||||||
String allocationAlgorithm = configs.get("vm.allocation.algorithm");
|
|
||||||
if (allocationAlgorithm != null) {
|
|
||||||
this.allocationAlgorithm = allocationAlgorithm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,12 +18,16 @@ package org.apache.cloudstack.storage.allocator;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||||
@ -45,7 +49,7 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
|||||||
@Inject
|
@Inject
|
||||||
private DataStoreManager dataStoreMgr;
|
private DataStoreManager dataStoreMgr;
|
||||||
@Inject
|
@Inject
|
||||||
private CapacityDao capacityDao;
|
protected CapacityDao capacityDao;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
|
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
|
||||||
@ -122,9 +126,16 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Long> poolIdsByCapacity = capacityDao.orderHostsByFreeCapacity(zoneId, null, capacityType);
|
Pair<List<Long>, Map<Long, Double>> result = capacityDao.orderHostsByFreeCapacity(zoneId, null, capacityType);
|
||||||
|
List<Long> poolIdsByCapacity = result.first();
|
||||||
|
Map<Long, String> sortedHostByCapacity = result.second().entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, entry -> decimalFormat.format(entry.getValue() * 100) + "%",
|
||||||
|
(e1, e2) -> e1, LinkedHashMap::new));
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("List of zone-wide storage pools in descending order of free capacity: "+ poolIdsByCapacity);
|
logger.debug("List of zone-wide storage pools: [{}] in descending order of free capacity (percentage): {}",
|
||||||
|
poolIdsByCapacity, sortedHostByCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
//now filter the given list of Pools by this ordered list
|
//now filter the given list of Pools by this ordered list
|
||||||
|
|||||||
@ -17,13 +17,19 @@
|
|||||||
package org.apache.cloudstack.storage.allocator;
|
package org.apache.cloudstack.storage.allocator;
|
||||||
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
import com.cloud.capacity.Capacity;
|
||||||
|
import com.cloud.capacity.dao.CapacityDao;
|
||||||
import java.util.ArrayList;
|
import com.cloud.deploy.DeploymentPlan;
|
||||||
import java.util.HashSet;
|
import com.cloud.deploy.DeploymentPlanner;
|
||||||
import java.util.List;
|
import com.cloud.storage.Storage;
|
||||||
import java.util.Set;
|
import com.cloud.storage.StoragePool;
|
||||||
|
import com.cloud.storage.dao.VolumeDao;
|
||||||
|
import com.cloud.user.Account;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import com.cloud.vm.DiskProfile;
|
||||||
|
import com.cloud.vm.VirtualMachineProfile;
|
||||||
|
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
@ -34,14 +40,18 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
import com.cloud.deploy.DeploymentPlan;
|
import java.lang.reflect.Field;
|
||||||
import com.cloud.deploy.DeploymentPlanner;
|
import java.util.ArrayList;
|
||||||
import com.cloud.storage.Storage;
|
import java.util.Arrays;
|
||||||
import com.cloud.storage.StoragePool;
|
import java.util.HashMap;
|
||||||
import com.cloud.storage.dao.VolumeDao;
|
import java.util.HashSet;
|
||||||
import com.cloud.user.Account;
|
import java.util.List;
|
||||||
import com.cloud.vm.DiskProfile;
|
import java.util.Map;
|
||||||
import com.cloud.vm.VirtualMachineProfile;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class AbstractStoragePoolAllocatorTest {
|
public class AbstractStoragePoolAllocatorTest {
|
||||||
@ -53,6 +63,10 @@ public class AbstractStoragePoolAllocatorTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
Account account;
|
Account account;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
CapacityDao capacityDao;
|
||||||
|
|
||||||
private List<StoragePool> pools;
|
private List<StoragePool> pools;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
@ -73,7 +87,8 @@ public class AbstractStoragePoolAllocatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reorderStoragePoolsBasedOnAlgorithm_random() {
|
public void reorderStoragePoolsBasedOnAlgorithm_random() throws Exception {
|
||||||
|
overrideDefaultConfigValue( VolumeOrchestrationService.VolumeAllocationAlgorithm, "random");
|
||||||
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
|
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
|
||||||
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
|
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
|
||||||
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByNumberOfVolumes(plan, pools, account);
|
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByNumberOfVolumes(plan, pools, account);
|
||||||
@ -81,8 +96,8 @@ public class AbstractStoragePoolAllocatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing() {
|
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing() throws Exception {
|
||||||
allocator.allocationAlgorithm = "userdispersing";
|
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "userdispersing");
|
||||||
Mockito.doReturn(pools).when(allocator).reorderPoolsByNumberOfVolumes(plan, pools, account);
|
Mockito.doReturn(pools).when(allocator).reorderPoolsByNumberOfVolumes(plan, pools, account);
|
||||||
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
|
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
|
||||||
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
|
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
|
||||||
@ -91,10 +106,9 @@ public class AbstractStoragePoolAllocatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing_reorder_check() {
|
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing_reorder_check() throws Exception {
|
||||||
allocator.allocationAlgorithm = "userdispersing";
|
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "userdispersing");
|
||||||
allocator.volumeDao = volumeDao;
|
allocator.volumeDao = volumeDao;
|
||||||
|
|
||||||
when(plan.getDataCenterId()).thenReturn(1l);
|
when(plan.getDataCenterId()).thenReturn(1l);
|
||||||
when(plan.getPodId()).thenReturn(1l);
|
when(plan.getPodId()).thenReturn(1l);
|
||||||
when(plan.getClusterId()).thenReturn(1l);
|
when(plan.getClusterId()).thenReturn(1l);
|
||||||
@ -114,8 +128,8 @@ public class AbstractStoragePoolAllocatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reorderStoragePoolsBasedOnAlgorithm_firstfitleastconsumed() {
|
public void reorderStoragePoolsBasedOnAlgorithm_firstfitleastconsumed() throws Exception {
|
||||||
allocator.allocationAlgorithm = "firstfitleastconsumed";
|
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "firstfitleastconsumed");
|
||||||
Mockito.doReturn(pools).when(allocator).reorderPoolsByCapacity(plan, pools);
|
Mockito.doReturn(pools).when(allocator).reorderPoolsByCapacity(plan, pools);
|
||||||
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
|
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
|
||||||
Mockito.verify(allocator, Mockito.times(1)).reorderPoolsByCapacity(plan, pools);
|
Mockito.verify(allocator, Mockito.times(1)).reorderPoolsByCapacity(plan, pools);
|
||||||
@ -132,6 +146,34 @@ public class AbstractStoragePoolAllocatorTest {
|
|||||||
}
|
}
|
||||||
Assert.assertTrue(firstchoice.size() > 2);
|
Assert.assertTrue(firstchoice.size() > 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reorderStoragePoolsBasedOnAlgorithmFirstFitLeastConsumed() throws Exception {
|
||||||
|
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "firstfitleastconsumed");
|
||||||
|
when(plan.getDataCenterId()).thenReturn(1L);
|
||||||
|
when(plan.getClusterId()).thenReturn(1L);
|
||||||
|
StoragePool pool1 = mock(StoragePool.class);
|
||||||
|
StoragePool pool2 = mock(StoragePool.class);
|
||||||
|
when(pool1.getId()).thenReturn(1L);
|
||||||
|
when(pool2.getId()).thenReturn(2L);
|
||||||
|
List<StoragePool> pools = Arrays.asList(pool1, pool2);
|
||||||
|
List<Long> poolIds = Arrays.asList(2L, 1L);
|
||||||
|
Map<Long, Double> hostCapacityMap = new HashMap<>();
|
||||||
|
hostCapacityMap.put(1L, 8.0);
|
||||||
|
hostCapacityMap.put(2L, 8.5);
|
||||||
|
Pair<List<Long>, Map<Long, Double>> poolsOrderedByCapacity = new Pair<>(poolIds, hostCapacityMap);
|
||||||
|
|
||||||
|
allocator.capacityDao = capacityDao;
|
||||||
|
Mockito.when(capacityDao.orderHostsByFreeCapacity(1L, 1L, Capacity.CAPACITY_TYPE_LOCAL_STORAGE)).thenReturn(poolsOrderedByCapacity);
|
||||||
|
List<StoragePool> result = allocator.reorderPoolsByCapacity(plan, pools);
|
||||||
|
assertEquals(Arrays.asList(pool2, pool1), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void overrideDefaultConfigValue(final ConfigKey configKey, final String value) throws IllegalAccessException, NoSuchFieldException {
|
||||||
|
final Field f = ConfigKey.class.getDeclaredField("_defaultValue");
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(configKey, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockStorapoolAllocater extends AbstractStoragePoolAllocator {
|
class MockStorapoolAllocater extends AbstractStoragePoolAllocator {
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.cloudstack.storage.allocator;
|
||||||
|
|
||||||
|
import com.cloud.capacity.Capacity;
|
||||||
|
import com.cloud.capacity.dao.CapacityDao;
|
||||||
|
import com.cloud.deploy.DeploymentPlan;
|
||||||
|
import com.cloud.storage.Storage;
|
||||||
|
import com.cloud.storage.StoragePool;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ZoneWideStoragePoolAllocatorTest {
|
||||||
|
private ZoneWideStoragePoolAllocator allocator;
|
||||||
|
private DeploymentPlan plan;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
allocator = new ZoneWideStoragePoolAllocator();
|
||||||
|
plan = mock(DeploymentPlan.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReorderPoolsByCapacity() {
|
||||||
|
when(plan.getDataCenterId()).thenReturn(1L);
|
||||||
|
when(plan.getClusterId()).thenReturn(null);
|
||||||
|
StoragePool pool1 = mock(StoragePool.class);
|
||||||
|
StoragePool pool2 = mock(StoragePool.class);
|
||||||
|
when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.Filesystem);
|
||||||
|
when(pool1.getId()).thenReturn(1L);
|
||||||
|
when(pool2.getId()).thenReturn(2L);
|
||||||
|
List<StoragePool> pools = Arrays.asList(pool1, pool2);
|
||||||
|
List<Long> poolIds = Arrays.asList(2L, 1L);
|
||||||
|
Map<Long, Double> hostCapacityMap = new HashMap<>();
|
||||||
|
hostCapacityMap.put(1L, 8.0);
|
||||||
|
hostCapacityMap.put(2L, 8.5);
|
||||||
|
Pair<List<Long>, Map<Long, Double>> poolsOrderedByCapacity = new Pair<>(poolIds, hostCapacityMap);
|
||||||
|
CapacityDao capacityDao = mock(CapacityDao.class);
|
||||||
|
Mockito.when(capacityDao.orderHostsByFreeCapacity(1L, null, Capacity.CAPACITY_TYPE_LOCAL_STORAGE)).thenReturn(poolsOrderedByCapacity);
|
||||||
|
allocator.capacityDao = capacityDao;
|
||||||
|
List<StoragePool> result = allocator.reorderPoolsByCapacity(plan, pools);
|
||||||
|
assertEquals(Arrays.asList(pool2, pool1), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,19 +16,19 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package com.cloud.agent.manager.allocator.impl;
|
package com.cloud.agent.manager.allocator.impl;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.naming.ConfigurationException;
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
|
||||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.cloud.agent.manager.allocator.HostAllocator;
|
import com.cloud.agent.manager.allocator.HostAllocator;
|
||||||
import com.cloud.capacity.CapacityManager;
|
import com.cloud.capacity.CapacityManager;
|
||||||
import com.cloud.capacity.CapacityVO;
|
import com.cloud.capacity.CapacityVO;
|
||||||
@ -37,6 +37,7 @@ import com.cloud.configuration.Config;
|
|||||||
import com.cloud.dc.ClusterDetailsDao;
|
import com.cloud.dc.ClusterDetailsDao;
|
||||||
import com.cloud.dc.dao.ClusterDao;
|
import com.cloud.dc.dao.ClusterDao;
|
||||||
import com.cloud.deploy.DeploymentPlan;
|
import com.cloud.deploy.DeploymentPlan;
|
||||||
|
import com.cloud.deploy.DeploymentClusterPlanner;
|
||||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||||
import com.cloud.gpu.GPU;
|
import com.cloud.gpu.GPU;
|
||||||
import com.cloud.host.DetailVO;
|
import com.cloud.host.DetailVO;
|
||||||
@ -63,6 +64,10 @@ import com.cloud.vm.VirtualMachineProfile;
|
|||||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||||
import com.cloud.vm.dao.VMInstanceDao;
|
import com.cloud.vm.dao.VMInstanceDao;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
|
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An allocator that tries to find a fit on a computing host. This allocator does not care whether or not the host supports routing.
|
* An allocator that tries to find a fit on a computing host. This allocator does not care whether or not the host supports routing.
|
||||||
@ -97,8 +102,7 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
|
|||||||
UserVmDetailsDao _userVmDetailsDao;
|
UserVmDetailsDao _userVmDetailsDao;
|
||||||
|
|
||||||
boolean _checkHvm = true;
|
boolean _checkHvm = true;
|
||||||
protected String _allocationAlgorithm = "random";
|
static DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Host> allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) {
|
public List<Host> allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) {
|
||||||
@ -285,12 +289,13 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
|
|||||||
|
|
||||||
protected List<Host> allocateTo(DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List<? extends Host> hosts, int returnUpTo,
|
protected List<Host> allocateTo(DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List<? extends Host> hosts, int returnUpTo,
|
||||||
boolean considerReservedCapacity, Account account) {
|
boolean considerReservedCapacity, Account account) {
|
||||||
if (_allocationAlgorithm.equals("random") || _allocationAlgorithm.equals("userconcentratedpod_random")) {
|
String vmAllocationAlgorithm = DeploymentClusterPlanner.VmAllocationAlgorithm.value();
|
||||||
|
if (vmAllocationAlgorithm.equals("random") || vmAllocationAlgorithm.equals("userconcentratedpod_random")) {
|
||||||
// Shuffle this so that we don't check the hosts in the same order.
|
// Shuffle this so that we don't check the hosts in the same order.
|
||||||
Collections.shuffle(hosts);
|
Collections.shuffle(hosts);
|
||||||
} else if (_allocationAlgorithm.equals("userdispersing")) {
|
} else if (vmAllocationAlgorithm.equals("userdispersing")) {
|
||||||
hosts = reorderHostsByNumberOfVms(plan, hosts, account);
|
hosts = reorderHostsByNumberOfVms(plan, hosts, account);
|
||||||
}else if(_allocationAlgorithm.equals("firstfitleastconsumed")){
|
}else if(vmAllocationAlgorithm.equals("firstfitleastconsumed")){
|
||||||
hosts = reorderHostsByCapacity(plan, hosts);
|
hosts = reorderHostsByCapacity(plan, hosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,9 +378,16 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
|
|||||||
if("RAM".equalsIgnoreCase(capacityTypeToOrder)){
|
if("RAM".equalsIgnoreCase(capacityTypeToOrder)){
|
||||||
capacityType = CapacityVO.CAPACITY_TYPE_MEMORY;
|
capacityType = CapacityVO.CAPACITY_TYPE_MEMORY;
|
||||||
}
|
}
|
||||||
List<Long> hostIdsByFreeCapacity = _capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType);
|
Pair<List<Long>, Map<Long, Double>> result = _capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType);
|
||||||
|
List<Long> hostIdsByFreeCapacity = result.first();
|
||||||
|
Map<Long, String> sortedHostByCapacity = result.second().entrySet()
|
||||||
|
.stream()
|
||||||
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, entry -> decimalFormat.format(entry.getValue() * 100) + "%",
|
||||||
|
(e1, e2) -> e1, LinkedHashMap::new));
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("List of hosts in descending order of free capacity in the cluster: "+ hostIdsByFreeCapacity);
|
logger.debug("List of hosts: [{}] in descending order of free capacity (percentage) in the cluster: {}",
|
||||||
|
hostIdsByFreeCapacity, sortedHostByCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
//now filter the given list of Hosts by this ordered list
|
//now filter the given list of Hosts by this ordered list
|
||||||
@ -574,11 +586,6 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
|
|||||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||||
if (_configDao != null) {
|
if (_configDao != null) {
|
||||||
Map<String, String> configs = _configDao.getConfiguration(params);
|
Map<String, String> configs = _configDao.getConfiguration(params);
|
||||||
|
|
||||||
String allocationAlgorithm = configs.get("vm.allocation.algorithm");
|
|
||||||
if (allocationAlgorithm != null) {
|
|
||||||
_allocationAlgorithm = allocationAlgorithm;
|
|
||||||
}
|
|
||||||
String value = configs.get("xenserver.check.hvm");
|
String value = configs.get("xenserver.check.hvm");
|
||||||
_checkHvm = value == null ? true : Boolean.parseBoolean(value);
|
_checkHvm = value == null ? true : Boolean.parseBoolean(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* 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.manager.allocator.impl;
|
||||||
|
|
||||||
|
import com.cloud.capacity.CapacityManager;
|
||||||
|
import com.cloud.deploy.DeploymentPlan;
|
||||||
|
import com.cloud.deploy.DeploymentPlanner;
|
||||||
|
import com.cloud.host.Host;
|
||||||
|
import com.cloud.offering.ServiceOffering;
|
||||||
|
import com.cloud.resource.ResourceManager;
|
||||||
|
import com.cloud.service.dao.ServiceOfferingDetailsDao;
|
||||||
|
import com.cloud.user.Account;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyMap;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
|
public class FirstFitAllocatorTest {
|
||||||
|
private FirstFitAllocator allocator;
|
||||||
|
private CapacityManager capacityMgr;
|
||||||
|
private ServiceOfferingDetailsDao offeringDetailsDao;
|
||||||
|
private ResourceManager resourceMgr;
|
||||||
|
|
||||||
|
private DeploymentPlan plan;
|
||||||
|
private ServiceOffering offering;
|
||||||
|
private DeploymentPlanner.ExcludeList avoid;
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
private Host host1;
|
||||||
|
private Host host2;
|
||||||
|
ConfigurationDao configDao;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
allocator = new FirstFitAllocator();
|
||||||
|
capacityMgr = mock(CapacityManager.class);
|
||||||
|
offeringDetailsDao = mock(ServiceOfferingDetailsDao.class);
|
||||||
|
resourceMgr = mock(ResourceManager.class);
|
||||||
|
configDao = mock(ConfigurationDao.class);
|
||||||
|
|
||||||
|
allocator._capacityMgr = capacityMgr;
|
||||||
|
allocator._serviceOfferingDetailsDao = offeringDetailsDao;
|
||||||
|
allocator._resourceMgr = resourceMgr;
|
||||||
|
allocator._configDao = configDao;
|
||||||
|
|
||||||
|
plan = mock(DeploymentPlan.class);
|
||||||
|
offering = mock(ServiceOffering.class);
|
||||||
|
avoid = mock(DeploymentPlanner.ExcludeList.class);
|
||||||
|
account = mock(Account.class);
|
||||||
|
|
||||||
|
host1 = mock(Host.class);
|
||||||
|
host2 = mock(Host.class);
|
||||||
|
|
||||||
|
when(plan.getDataCenterId()).thenReturn(1L);
|
||||||
|
when(offering.getCpu()).thenReturn(2);
|
||||||
|
when(offering.getSpeed()).thenReturn(1000);
|
||||||
|
when(offering.getRamSize()).thenReturn(2048);
|
||||||
|
when(offering.getId()).thenReturn(123L);
|
||||||
|
when(offering.getHostTag()).thenReturn(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfigure() throws Exception {
|
||||||
|
when(configDao.getConfiguration(anyMap())).thenReturn(new HashMap<>());
|
||||||
|
assertTrue(allocator._checkHvm);
|
||||||
|
assertTrue(allocator.configure("test", new HashMap<>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateTo_SuccessfulMatch() {
|
||||||
|
List<Host> inputHosts = Arrays.asList(host1, host2);
|
||||||
|
|
||||||
|
// All hosts are allowed
|
||||||
|
when(avoid.shouldAvoid(host1)).thenReturn(false);
|
||||||
|
when(avoid.shouldAvoid(host2)).thenReturn(false);
|
||||||
|
|
||||||
|
// No GPU requirement
|
||||||
|
when(offeringDetailsDao.findDetail(eq(123L), anyString())).thenReturn(null);
|
||||||
|
|
||||||
|
// CPU capability and capacity is met
|
||||||
|
when(capacityMgr.checkIfHostReachMaxGuestLimit(any())).thenReturn(false);
|
||||||
|
when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(eq(host1), eq(offering), eq(true)))
|
||||||
|
.thenReturn(new Pair<>(true, true));
|
||||||
|
when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(eq(host2), eq(offering), eq(true)))
|
||||||
|
.thenReturn(new Pair<>(true, false));
|
||||||
|
|
||||||
|
List<Host> result = allocator.allocateTo(plan, offering, null, avoid, inputHosts, 2, true, account);
|
||||||
|
|
||||||
|
// Only host1 should be returned
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertTrue(result.contains(host1));
|
||||||
|
assertFalse(result.contains(host2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateTo_AvoidSetAndGuestLimit() {
|
||||||
|
List<Host> inputHosts = Arrays.asList(host1, host2);
|
||||||
|
|
||||||
|
when(avoid.shouldAvoid(host1)).thenReturn(true); // Avoided
|
||||||
|
when(avoid.shouldAvoid(host2)).thenReturn(false);
|
||||||
|
|
||||||
|
when(capacityMgr.checkIfHostReachMaxGuestLimit(host2)).thenReturn(true); // Reached limit
|
||||||
|
|
||||||
|
List<Host> result = allocator.allocateTo(plan, offering, null, avoid, inputHosts, 2, true, account);
|
||||||
|
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateTo_GPUNotAvailable() {
|
||||||
|
List<Host> inputHosts = Arrays.asList(host1);
|
||||||
|
when(avoid.shouldAvoid(host1)).thenReturn(false);
|
||||||
|
|
||||||
|
// GPU required but not available
|
||||||
|
var vgpuDetail = mock(com.cloud.service.ServiceOfferingDetailsVO.class);
|
||||||
|
var pciDetail = mock(com.cloud.service.ServiceOfferingDetailsVO.class);
|
||||||
|
when(offeringDetailsDao.findDetail(eq(123L), eq("vgpuType"))).thenReturn(vgpuDetail);
|
||||||
|
when(offeringDetailsDao.findDetail(eq(123L), eq("pciDevice"))).thenReturn(pciDetail);
|
||||||
|
when(pciDetail.getValue()).thenReturn("NVIDIA");
|
||||||
|
when(vgpuDetail.getValue()).thenReturn("GRID");
|
||||||
|
|
||||||
|
when(resourceMgr.isGPUDeviceAvailable(eq(host1), eq("NVIDIA"), eq("GRID"))).thenReturn(false);
|
||||||
|
|
||||||
|
List<Host> result = allocator.allocateTo(plan, offering, null, avoid, inputHosts, 1, true, account);
|
||||||
|
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user