From 120ad26a407b8a7f6c6f477679525616f52ada77 Mon Sep 17 00:00:00 2001 From: Vishesh Date: Mon, 10 Jun 2024 18:14:20 +0530 Subject: [PATCH] Ensure affinity groups are honored when VMs are deployed in parallel --- .../affinity/dao/AffinityGroupDao.java | 3 + .../affinity/dao/AffinityGroupDaoImpl.java | 12 ++ .../affinity/HostAffinityProcessor.java | 39 ++++-- .../affinity/HostAntiAffinityProcessor.java | 114 +++++++++++------- 4 files changed, 116 insertions(+), 52 deletions(-) diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java index 010720ba33aa..859b29215044 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java @@ -38,4 +38,7 @@ public interface AffinityGroupDao extends GenericDao { AffinityGroupVO findByAccountAndType(Long accountId, String string); AffinityGroupVO findDomainLevelGroupByType(Long domainId, String string); + + List listByIds(List ids, boolean exclusive); + } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java index 3bd7c6d082b2..5bd598f36a0f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java @@ -31,6 +31,7 @@ import com.cloud.utils.db.SearchCriteria; public class AffinityGroupDaoImpl extends GenericDaoBase implements AffinityGroupDao { + private SearchBuilder IdsSearch; private SearchBuilder AccountIdSearch; private SearchBuilder AccountIdNameSearch; private SearchBuilder AccountIdNamesSearch; @@ -47,6 +48,10 @@ public AffinityGroupDaoImpl() { @PostConstruct protected void init() { + IdsSearch = createSearchBuilder(); + IdsSearch.and("idIn", IdsSearch.entity().getId(), SearchCriteria.Op.IN); + IdsSearch.done(); + AccountIdSearch = createSearchBuilder(); AccountIdSearch.and("accountId", AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); AccountIdSearch.done(); @@ -158,4 +163,11 @@ public AffinityGroupVO findDomainLevelGroupByType(Long domainId, String type) { sc.setJoinParameters("domainTypeSearch", "domainId", domainId); return findOneBy(sc); } + + @Override + public List listByIds(List ids, boolean exclusive) { + SearchCriteria sc = IdsSearch.create(); + sc.setParameters("idIn", ids.toArray()); + return lockRows(sc, null, exclusive); + } } diff --git a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java index 07c1dd5ff884..072eff09215c 100644 --- a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java @@ -23,9 +23,14 @@ import java.util.Set; import java.util.HashSet; import java.util.ArrayList; +import java.util.stream.Collectors; import javax.inject.Inject; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; @@ -56,9 +61,16 @@ public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, Exclud VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); if (CollectionUtils.isNotEmpty(vmGroupMappings)) { - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - processAffinityGroup(vmGroupMapping, plan, vm, vmList); - } + List affinityGroupIdList = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIdList, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + processAffinityGroup(vmGroupMapping, plan, vm, vmList); + } + } + }); } } @@ -132,16 +144,23 @@ public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedD long plannedHostId = plannedDestination.getHost().getId(); VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + if (CollectionUtils.isEmpty(vmGroupMappings)) { + return true; + } + List affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) { + return false; + } - if (CollectionUtils.isNotEmpty(vmGroupMappings)) { - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) { - return false; } + return true; } - } - - return true; + }); } /** diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java index 2a3c5796dda4..970632906c64 100644 --- a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -19,10 +19,17 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.DateUtil; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; @@ -36,7 +43,6 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.exception.AffinityConflictException; -import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -67,40 +73,54 @@ public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, Exclud VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - if (vmGroupMapping != null) { - AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); - - if (s_logger.isDebugEnabled()) { - s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); + if (CollectionUtils.isEmpty(vmGroupMappings)) { + return; + } + List affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + processAffinityGroup(vmGroupMapping, avoid, vm); } + } + }); - List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); - groupVMIds.remove(vm.getId()); + } - for (Long groupVMId : groupVMIds) { - VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); - if (groupVM != null && !groupVM.isRemoved()) { - if (groupVM.getHostId() != null) { - avoid.addHost(groupVM.getHostId()); - if (s_logger.isDebugEnabled()) { - s_logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host"); - } - } else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) { - long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000; - if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { - avoid.addHost(groupVM.getLastHostId()); - if (s_logger.isDebugEnabled()) { - s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() + - " is present on the host, in Stopped state but has reserved capacity"); - } - } + protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, ExcludeList avoid, VirtualMachine vm) { + if (vmGroupMapping != null) { + AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); + } + + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); + if (groupVM != null && !groupVM.isRemoved()) { + if (groupVM.getHostId() != null) { + avoid.addHost(groupVM.getHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host"); + } + } + } else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000; + if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { + avoid.addHost(groupVM.getLastHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() + + " is present on the host, in Stopped state but has reserved capacity"); } } } } } - } @Override @@ -121,25 +141,35 @@ public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedD VirtualMachine vm = vmProfile.getVirtualMachine(); List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + if (CollectionUtils.isEmpty(vmGroupMappings)) { + return true; + } - for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { - // if more than 1 VM's are present in the group then check for - // conflict due to parallel deployment - List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId()); - groupVMIds.remove(vm.getId()); - - for (Long groupVMId : groupVMIds) { - VMReservationVO vmReservation = _reservationDao.findByVmId(groupVMId); - if (vmReservation != null && vmReservation.getHostId() != null && vmReservation.getHostId().equals(plannedHostId)) { - if (s_logger.isDebugEnabled()) { - s_logger.debug("Planned destination for VM " + vm.getId() + " conflicts with an existing VM " + vmReservation.getVmId() + - " reserved on the same host " + plannedHostId); + List affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList()); + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + _affinityGroupDao.listByIds(affinityGroupIds, true); + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + // if more than 1 VM's are present in the group then check for + // conflict due to parallel deployment + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMReservationVO vmReservation = _reservationDao.findByVmId(groupVMId); + if (vmReservation != null && vmReservation.getHostId() != null && vmReservation.getHostId().equals(plannedHostId)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Planned destination for VM " + vm.getId() + " conflicts with an existing VM " + vmReservation.getVmId() + + " reserved on the same host " + plannedHostId); + } + return false; + } } - return false; } + return true; } - } - return true; + }); } }