Introduce volume allocation algorithm global configuration (#10696)

This commit is contained in:
Manoj Kumar 2025-05-16 17:36:42 +05:30 committed by GitHub
parent 7bab40db6f
commit d5ba23c848
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1068 additions and 140 deletions

View File

@ -62,7 +62,7 @@ public interface DeploymentClusterPlanner extends DeploymentPlanner {
"vm.allocation.algorithm",
"Advanced",
"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,
ConfigKey.Scope.Global, null, null, null, null, null,
ConfigKey.Kind.Select,

View File

@ -84,6 +84,17 @@ public interface VolumeOrchestrationService {
"The maximum size for a volume (in GiB).",
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)
throws ConcurrentOperationException, StorageUnavailableException;

View File

@ -38,6 +38,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.deploy.DeploymentClusterPlanner;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.storage.DiskOfferingVO;
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.ConfigKey;
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.impl.AsyncJobVO;
import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
@ -262,6 +264,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
StoragePoolHostDao storagePoolHostDao;
@Inject
DiskOfferingDao diskOfferingDao;
@Inject
ConfigDepot configDepot;
@Inject
ConfigurationDao configurationDao;
@Inject
protected SnapshotHelper snapshotHelper;
@ -2047,7 +2053,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
@Override
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
@ -2060,6 +2068,18 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
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) {
VolumeVO volume = _volsDao.findById(volumeId);
if (volume == null) {

View File

@ -20,14 +20,29 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
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.offering.DiskOffering;
import com.cloud.storage.ScopeType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage;
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.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 org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
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.SnapshotDataStoreDao;
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.junit.Assert;
import org.junit.Before;
@ -53,14 +75,9 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import com.cloud.configuration.Resource;
import com.cloud.exception.StorageAccessException;
import com.cloud.host.Host;
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;
import static org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService.VolumeAllocationAlgorithm;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(MockitoJUnitRunner.class)
public class VolumeOrchestratorTest {
@ -73,6 +90,17 @@ public class VolumeOrchestratorTest {
protected VolumeDataFactory volumeDataFactory;
@Mock
protected VolumeDao volumeDao;
@Mock
protected PassphraseDao passphraseDao;
@Mock
protected PrimaryDataStoreDao storagePoolDao;
@Mock
protected EntityManager entityMgr;
@Mock
ConfigDepot configDepot;
@Mock
ConfigurationDao configurationDao;
@Mock
private SnapshotDataStoreDao snapshotDataStoreDaoMock;
@ -87,6 +115,9 @@ public class VolumeOrchestratorTest {
private static final Long DEFAULT_ACCOUNT_PS_RESOURCE_COUNT = 100L;
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
public void setUp() throws Exception {
@ -224,6 +255,349 @@ public class VolumeOrchestratorTest {
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
public void getVolumeCheckpointPathsAndImageStoreUrlsTestReturnEmptyListsIfNotKVM() {
Pair<List<String>, Set<String>> result = volumeOrchestrator.getVolumeCheckpointPathsAndImageStoreUrls(0, Hypervisor.HypervisorType.VMware);

View File

@ -64,5 +64,5 @@ public interface CapacityDao extends GenericDao<CapacityVO, Long> {
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);
}

View File

@ -1028,10 +1028,11 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
}
@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();
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);
if (zoneId != null) {
sql.append(" AND data_center_id = ?");
@ -1054,9 +1055,11 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
ResultSet rs = pstmt.executeQuery();
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) {
throw new CloudRuntimeException("DB Exception on: " + sql, e);
} catch (Throwable e) {

View File

@ -16,31 +16,44 @@
// under the License.
package com.cloud.capacity.dao;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.cloud.capacity.CapacityVO;
import com.cloud.host.Host;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.TransactionLegacy;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.capacity.CapacityVO;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
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)
public class CapacityDaoImplTest {
@ -48,6 +61,14 @@ public class CapacityDaoImplTest {
@InjectMocks
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 SearchCriteria<CapacityVO> searchCriteria;
@ -59,6 +80,16 @@ public class CapacityDaoImplTest {
searchCriteria = mock(SearchCriteria.class);
doReturn(searchBuilder).when(capacityDao).createSearchBuilder();
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
@ -96,4 +127,207 @@ public class CapacityDaoImplTest {
verify(capacityDao).listBy(searchCriteria);
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());
}
}

View File

@ -16,64 +16,68 @@
// under the License.
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.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.dao.CapacityDao;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.StorageUtil;
import com.cloud.storage.Volume;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.AdapterBase;
import com.cloud.vm.DiskProfile;
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 {
protected BigDecimal storageOverprovisioningFactor = new BigDecimal(1);
protected String allocationAlgorithm = "random";
protected long extraBytesPerVolume = 0;
static DecimalFormat decimalFormat = new DecimalFormat("#.##");
@Inject protected DataStoreManager dataStoreMgr;
@Inject protected PrimaryDataStoreDao storagePoolDao;
@Inject protected VolumeDao volumeDao;
@Inject protected ConfigurationDao configDao;
@Inject private CapacityDao capacityDao;
@Inject protected CapacityDao capacityDao;
@Inject private ClusterDao clusterDao;
@Inject private StorageManager storageMgr;
@Inject private StorageUtil storageUtil;
@ -95,10 +99,6 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
String globalStorageOverprovisioningFactor = configs.get("storage.overprovisioning.factor");
storageOverprovisioningFactor = new BigDecimal(NumbersUtil.parseFloat(globalStorageOverprovisioningFactor, 2.0f));
extraBytesPerVolume = 0;
String allocationAlgorithm = configs.get("vm.allocation.algorithm");
if (allocationAlgorithm != null) {
this.allocationAlgorithm = allocationAlgorithm;
}
return true;
}
return false;
@ -142,12 +142,16 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
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<>();
for (StoragePool pool : pools) {
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) {
logger.debug(String.format("Using allocation algorithm [%s] to reorder pools.", allocationAlgorithm));
if (allocationAlgorithm.equals("random") || allocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
String volumeAllocationAlgorithm = VolumeOrchestrationService.VolumeAllocationAlgorithm.value();
logger.debug("Using volume allocation algorithm {} to reorder pools.", volumeAllocationAlgorithm);
if (volumeAllocationAlgorithm.equals("random") || volumeAllocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
reorderRandomPools(pools);
} else if (StringUtils.equalsAny(allocationAlgorithm, "userdispersing", "firstfitleastconsumed")) {
} else if (StringUtils.equalsAny(volumeAllocationAlgorithm, "userdispersing", "firstfitleastconsumed")) {
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);
} else {
pools = reorderPoolsByCapacity(plan, pools);
@ -248,7 +252,7 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement
void reorderRandomPools(List<StoragePool> pools) {
StorageUtil.traceLogStoragePools(pools, logger, "pools to choose from: ");
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: ");
Collections.shuffle(pools, secureRandom);

View File

@ -16,27 +16,27 @@
// under the License.
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.DeploymentPlanner.ExcludeList;
import com.cloud.offering.ServiceOffering;
import com.cloud.storage.ScopeType;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VolumeApiServiceImpl;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.vm.DiskProfile;
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
public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocator {
@ -116,14 +116,6 @@ public class ClusterScopeStoragePoolAllocator extends AbstractStoragePoolAllocat
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
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;
}
}

View File

@ -18,12 +18,16 @@ package org.apache.cloudstack.storage.allocator;
import java.util.ArrayList;
import java.util.Arrays;
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;
import javax.inject.Inject;
import com.cloud.utils.Pair;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
@ -45,7 +49,7 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator {
@Inject
private DataStoreManager dataStoreMgr;
@Inject
private CapacityDao capacityDao;
protected CapacityDao capacityDao;
@Override
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;
}
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()) {
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

View File

@ -17,13 +17,19 @@
package org.apache.cloudstack.storage.allocator;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.cloud.capacity.Capacity;
import com.cloud.capacity.dao.CapacityDao;
import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentPlanner;
import com.cloud.storage.Storage;
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.junit.After;
import org.junit.Assert;
@ -34,14 +40,18 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentPlanner;
import com.cloud.storage.Storage;
import com.cloud.storage.StoragePool;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.vm.DiskProfile;
import com.cloud.vm.VirtualMachineProfile;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class AbstractStoragePoolAllocatorTest {
@ -53,6 +63,10 @@ public class AbstractStoragePoolAllocatorTest {
@Mock
Account account;
@Mock
CapacityDao capacityDao;
private List<StoragePool> pools;
@Mock
@ -73,7 +87,8 @@ public class AbstractStoragePoolAllocatorTest {
}
@Test
public void reorderStoragePoolsBasedOnAlgorithm_random() {
public void reorderStoragePoolsBasedOnAlgorithm_random() throws Exception {
overrideDefaultConfigValue( VolumeOrchestrationService.VolumeAllocationAlgorithm, "random");
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByNumberOfVolumes(plan, pools, account);
@ -81,8 +96,8 @@ public class AbstractStoragePoolAllocatorTest {
}
@Test
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing() {
allocator.allocationAlgorithm = "userdispersing";
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing() throws Exception {
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "userdispersing");
Mockito.doReturn(pools).when(allocator).reorderPoolsByNumberOfVolumes(plan, pools, account);
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
Mockito.verify(allocator, Mockito.times(0)).reorderPoolsByCapacity(plan, pools);
@ -91,10 +106,9 @@ public class AbstractStoragePoolAllocatorTest {
}
@Test
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing_reorder_check() {
allocator.allocationAlgorithm = "userdispersing";
public void reorderStoragePoolsBasedOnAlgorithm_userdispersing_reorder_check() throws Exception {
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "userdispersing");
allocator.volumeDao = volumeDao;
when(plan.getDataCenterId()).thenReturn(1l);
when(plan.getPodId()).thenReturn(1l);
when(plan.getClusterId()).thenReturn(1l);
@ -114,8 +128,8 @@ public class AbstractStoragePoolAllocatorTest {
}
@Test
public void reorderStoragePoolsBasedOnAlgorithm_firstfitleastconsumed() {
allocator.allocationAlgorithm = "firstfitleastconsumed";
public void reorderStoragePoolsBasedOnAlgorithm_firstfitleastconsumed() throws Exception {
overrideDefaultConfigValue(VolumeOrchestrationService.VolumeAllocationAlgorithm, "firstfitleastconsumed");
Mockito.doReturn(pools).when(allocator).reorderPoolsByCapacity(plan, pools);
allocator.reorderStoragePoolsBasedOnAlgorithm(pools, plan, account);
Mockito.verify(allocator, Mockito.times(1)).reorderPoolsByCapacity(plan, pools);
@ -132,6 +146,34 @@ public class AbstractStoragePoolAllocatorTest {
}
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 {

View File

@ -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);
}
}

View File

@ -16,19 +16,19 @@
// under the License.
package com.cloud.agent.manager.allocator.impl;
import java.text.DecimalFormat;
import java.util.ArrayList;
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;
import javax.inject.Inject;
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.capacity.CapacityManager;
import com.cloud.capacity.CapacityVO;
@ -37,6 +37,7 @@ import com.cloud.configuration.Config;
import com.cloud.dc.ClusterDetailsDao;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentClusterPlanner;
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.gpu.GPU;
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.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.
@ -97,8 +102,7 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
UserVmDetailsDao _userVmDetailsDao;
boolean _checkHvm = true;
protected String _allocationAlgorithm = "random";
static DecimalFormat decimalFormat = new DecimalFormat("#.##");
@Override
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,
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.
Collections.shuffle(hosts);
} else if (_allocationAlgorithm.equals("userdispersing")) {
} else if (vmAllocationAlgorithm.equals("userdispersing")) {
hosts = reorderHostsByNumberOfVms(plan, hosts, account);
}else if(_allocationAlgorithm.equals("firstfitleastconsumed")){
}else if(vmAllocationAlgorithm.equals("firstfitleastconsumed")){
hosts = reorderHostsByCapacity(plan, hosts);
}
@ -373,9 +378,16 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
if("RAM".equalsIgnoreCase(capacityTypeToOrder)){
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()) {
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
@ -574,11 +586,6 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator {
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
if (_configDao != null) {
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");
_checkHvm = value == null ? true : Boolean.parseBoolean(value);
}

View File

@ -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());
}
}