Skip to content

Commit c98f1b8

Browse files
authored
Ensure affinity groups are honored when VMs are deployed in parallel (#9201)
1 parent bf11676 commit c98f1b8

File tree

4 files changed

+116
-52
lines changed

4 files changed

+116
-52
lines changed

engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,7 @@ public interface AffinityGroupDao extends GenericDao<AffinityGroupVO, Long> {
3838
AffinityGroupVO findByAccountAndType(Long accountId, String string);
3939

4040
AffinityGroupVO findDomainLevelGroupByType(Long domainId, String string);
41+
42+
List<AffinityGroupVO> listByIds(List<Long> ids, boolean exclusive);
43+
4144
}

engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.cloud.utils.db.SearchCriteria;
3232

3333
public class AffinityGroupDaoImpl extends GenericDaoBase<AffinityGroupVO, Long> implements AffinityGroupDao {
34+
private SearchBuilder<AffinityGroupVO> IdsSearch;
3435
private SearchBuilder<AffinityGroupVO> AccountIdSearch;
3536
private SearchBuilder<AffinityGroupVO> AccountIdNameSearch;
3637
private SearchBuilder<AffinityGroupVO> AccountIdNamesSearch;
@@ -47,6 +48,10 @@ public AffinityGroupDaoImpl() {
4748

4849
@PostConstruct
4950
protected void init() {
51+
IdsSearch = createSearchBuilder();
52+
IdsSearch.and("idIn", IdsSearch.entity().getId(), SearchCriteria.Op.IN);
53+
IdsSearch.done();
54+
5055
AccountIdSearch = createSearchBuilder();
5156
AccountIdSearch.and("accountId", AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
5257
AccountIdSearch.done();
@@ -158,4 +163,11 @@ public AffinityGroupVO findDomainLevelGroupByType(Long domainId, String type) {
158163
sc.setJoinParameters("domainTypeSearch", "domainId", domainId);
159164
return findOneBy(sc);
160165
}
166+
167+
@Override
168+
public List<AffinityGroupVO> listByIds(List<Long> ids, boolean exclusive) {
169+
SearchCriteria<AffinityGroupVO> sc = IdsSearch.create();
170+
sc.setParameters("idIn", ids.toArray());
171+
return lockRows(sc, null, exclusive);
172+
}
161173
}

plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@
2323
import java.util.Set;
2424
import java.util.HashSet;
2525
import java.util.ArrayList;
26+
import java.util.stream.Collectors;
2627

2728
import javax.inject.Inject;
2829

30+
import com.cloud.utils.db.Transaction;
31+
import com.cloud.utils.db.TransactionCallback;
32+
import com.cloud.utils.db.TransactionCallbackNoReturn;
33+
import com.cloud.utils.db.TransactionStatus;
2934
import org.apache.commons.collections.CollectionUtils;
3035
import org.apache.log4j.Logger;
3136

@@ -56,9 +61,16 @@ public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, Exclud
5661
VirtualMachine vm = vmProfile.getVirtualMachine();
5762
List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
5863
if (CollectionUtils.isNotEmpty(vmGroupMappings)) {
59-
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
60-
processAffinityGroup(vmGroupMapping, plan, vm, vmList);
61-
}
64+
List<Long> affinityGroupIdList = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList());
65+
Transaction.execute(new TransactionCallbackNoReturn() {
66+
@Override
67+
public void doInTransactionWithoutResult(TransactionStatus status) {
68+
_affinityGroupDao.listByIds(affinityGroupIdList, true);
69+
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
70+
processAffinityGroup(vmGroupMapping, plan, vm, vmList);
71+
}
72+
}
73+
});
6274
}
6375
}
6476

@@ -132,16 +144,23 @@ public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedD
132144
long plannedHostId = plannedDestination.getHost().getId();
133145
VirtualMachine vm = vmProfile.getVirtualMachine();
134146
List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
147+
if (CollectionUtils.isEmpty(vmGroupMappings)) {
148+
return true;
149+
}
150+
List<Long> affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList());
151+
return Transaction.execute(new TransactionCallback<Boolean>() {
152+
@Override
153+
public Boolean doInTransaction(TransactionStatus status) {
154+
_affinityGroupDao.listByIds(affinityGroupIds, true);
155+
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
156+
if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) {
157+
return false;
158+
}
135159

136-
if (CollectionUtils.isNotEmpty(vmGroupMappings)) {
137-
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
138-
if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) {
139-
return false;
140160
}
161+
return true;
141162
}
142-
}
143-
144-
return true;
163+
});
145164
}
146165

147166
/**

plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@
1919
import java.util.Arrays;
2020
import java.util.List;
2121
import java.util.Map;
22+
import java.util.stream.Collectors;
2223

2324
import javax.inject.Inject;
2425
import javax.naming.ConfigurationException;
2526

27+
import com.cloud.utils.DateUtil;
28+
import com.cloud.utils.db.Transaction;
29+
import com.cloud.utils.db.TransactionCallback;
30+
import com.cloud.utils.db.TransactionCallbackNoReturn;
31+
import com.cloud.utils.db.TransactionStatus;
32+
import org.apache.commons.collections.CollectionUtils;
2633
import org.apache.log4j.Logger;
2734

2835
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
@@ -36,7 +43,6 @@
3643
import com.cloud.deploy.DeploymentPlan;
3744
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
3845
import com.cloud.exception.AffinityConflictException;
39-
import com.cloud.utils.DateUtil;
4046
import com.cloud.utils.NumbersUtil;
4147
import com.cloud.vm.VMInstanceVO;
4248
import com.cloud.vm.VirtualMachine;
@@ -67,40 +73,54 @@ public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, Exclud
6773
VirtualMachine vm = vmProfile.getVirtualMachine();
6874
List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
6975

70-
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
71-
if (vmGroupMapping != null) {
72-
AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId());
73-
74-
if (s_logger.isDebugEnabled()) {
75-
s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId());
76+
if (CollectionUtils.isEmpty(vmGroupMappings)) {
77+
return;
78+
}
79+
List<Long> affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList());
80+
Transaction.execute(new TransactionCallbackNoReturn() {
81+
@Override
82+
public void doInTransactionWithoutResult(TransactionStatus status) {
83+
_affinityGroupDao.listByIds(affinityGroupIds, true);
84+
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
85+
processAffinityGroup(vmGroupMapping, avoid, vm);
7686
}
87+
}
88+
});
7789

78-
List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId());
79-
groupVMIds.remove(vm.getId());
90+
}
8091

81-
for (Long groupVMId : groupVMIds) {
82-
VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId);
83-
if (groupVM != null && !groupVM.isRemoved()) {
84-
if (groupVM.getHostId() != null) {
85-
avoid.addHost(groupVM.getHostId());
86-
if (s_logger.isDebugEnabled()) {
87-
s_logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host");
88-
}
89-
} else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) {
90-
long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000;
91-
if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) {
92-
avoid.addHost(groupVM.getLastHostId());
93-
if (s_logger.isDebugEnabled()) {
94-
s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() +
95-
" is present on the host, in Stopped state but has reserved capacity");
96-
}
97-
}
92+
protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, ExcludeList avoid, VirtualMachine vm) {
93+
if (vmGroupMapping != null) {
94+
AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId());
95+
96+
if (s_logger.isDebugEnabled()) {
97+
s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId());
98+
}
99+
100+
List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId());
101+
groupVMIds.remove(vm.getId());
102+
103+
for (Long groupVMId : groupVMIds) {
104+
VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId);
105+
if (groupVM != null && !groupVM.isRemoved()) {
106+
if (groupVM.getHostId() != null) {
107+
avoid.addHost(groupVM.getHostId());
108+
if (s_logger.isDebugEnabled()) {
109+
s_logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host");
110+
}
111+
}
112+
} else if (Arrays.asList(VirtualMachine.State.Starting, VirtualMachine.State.Stopped).contains(groupVM.getState()) && groupVM.getLastHostId() != null) {
113+
long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000;
114+
if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) {
115+
avoid.addHost(groupVM.getLastHostId());
116+
if (s_logger.isDebugEnabled()) {
117+
s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() +
118+
" is present on the host, in Stopped state but has reserved capacity");
98119
}
99120
}
100121
}
101122
}
102123
}
103-
104124
}
105125

106126
@Override
@@ -121,25 +141,35 @@ public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedD
121141
VirtualMachine vm = vmProfile.getVirtualMachine();
122142

123143
List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType());
144+
if (CollectionUtils.isEmpty(vmGroupMappings)) {
145+
return true;
146+
}
124147

125-
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
126-
// if more than 1 VM's are present in the group then check for
127-
// conflict due to parallel deployment
128-
List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId());
129-
groupVMIds.remove(vm.getId());
130-
131-
for (Long groupVMId : groupVMIds) {
132-
VMReservationVO vmReservation = _reservationDao.findByVmId(groupVMId);
133-
if (vmReservation != null && vmReservation.getHostId() != null && vmReservation.getHostId().equals(plannedHostId)) {
134-
if (s_logger.isDebugEnabled()) {
135-
s_logger.debug("Planned destination for VM " + vm.getId() + " conflicts with an existing VM " + vmReservation.getVmId() +
136-
" reserved on the same host " + plannedHostId);
148+
List<Long> affinityGroupIds = vmGroupMappings.stream().map(AffinityGroupVMMapVO::getAffinityGroupId).collect(Collectors.toList());
149+
return Transaction.execute(new TransactionCallback<Boolean>() {
150+
@Override
151+
public Boolean doInTransaction(TransactionStatus status) {
152+
_affinityGroupDao.listByIds(affinityGroupIds, true);
153+
for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) {
154+
// if more than 1 VM's are present in the group then check for
155+
// conflict due to parallel deployment
156+
List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId());
157+
groupVMIds.remove(vm.getId());
158+
159+
for (Long groupVMId : groupVMIds) {
160+
VMReservationVO vmReservation = _reservationDao.findByVmId(groupVMId);
161+
if (vmReservation != null && vmReservation.getHostId() != null && vmReservation.getHostId().equals(plannedHostId)) {
162+
if (s_logger.isDebugEnabled()) {
163+
s_logger.debug("Planned destination for VM " + vm.getId() + " conflicts with an existing VM " + vmReservation.getVmId() +
164+
" reserved on the same host " + plannedHostId);
165+
}
166+
return false;
167+
}
137168
}
138-
return false;
139169
}
170+
return true;
140171
}
141-
}
142-
return true;
172+
});
143173
}
144174

145175
}

0 commit comments

Comments
 (0)