Skip to content

Commit 5054766

Browse files
server: Submitting multiple dynamic VM Scaling API commands for the same instance can result in two usage events in the same second causing a compound key violation in usage service (#3991)
Root cause: Even though dynamic scaling job is handled in vmworkjob queue which ensures serilizing multiple jobs but the database updating and generating usage events are out of the job queue. Solution: Moved all updations into the job queue Firstly I have tested all the scenarios to check if nothing is broken: Scaling on a running VM with normal compute offering Scaling on a stopped VM with normal compute offering Scaling on a running VM with custom compute offering Scaling on stopped VM with custom compute offering Scaling on stopped/running VM between custom compute offering and normal compute offering and combinations among these. Checked if the custom parameters have been populated or deleted accordingly based on the offering to which the VM is scaled Since this is a corner scenario I could not test the exact point where two usage events are recorded at the same time for two different API calls on same VM.
1 parent 6a683dc commit 5054766

7 files changed

Lines changed: 88 additions & 106 deletions

File tree

engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ void advanceReboot(String vmUuid, Map<VirtualMachineProfile.Param, Object> param
152152
* @param serviceOfferingId
153153
* @return
154154
*/
155-
boolean upgradeVmDb(long vmId, long serviceOfferingId);
155+
boolean upgradeVmDb(long vmId, ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering);
156156

157157
/**
158158
* @param vm
@@ -201,7 +201,7 @@ NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile request
201201
boolean replugNic(Network network, NicTO nic, VirtualMachineTO vm, ReservationContext context, DeployDestination dest) throws ConcurrentOperationException,
202202
ResourceUnavailableException, InsufficientCapacityException;
203203

204-
VirtualMachine reConfigureVm(String vmUuid, ServiceOffering newServiceOffering, boolean sameHost) throws ResourceUnavailableException, ConcurrentOperationException,
204+
VirtualMachine reConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, Map<String, String> customParameters, boolean sameHost) throws ResourceUnavailableException, ConcurrentOperationException,
205205
InsufficientServerCapacityException;
206206

207207
void findHostAndMigrate(String vmUuid, Long newSvcOfferingId, DeploymentPlanner.ExcludeList excludeHostList) throws InsufficientCapacityException,

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
import com.cloud.agent.api.PrepareForMigrationAnswer;
4343
import com.cloud.agent.api.to.DpdkTO;
44+
import com.cloud.event.UsageEventVO;
4445
import com.cloud.offering.NetworkOffering;
4546
import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
4647
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
@@ -232,6 +233,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
232233

233234
private static final String VM_SYNC_ALERT_SUBJECT = "VM state sync alert";
234235

236+
@Inject
237+
private UserVmManager _userVmMgr;
235238
@Inject
236239
private DataStoreManager dataStoreMgr;
237240
@Inject
@@ -3406,13 +3409,20 @@ public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffe
34063409
}
34073410

34083411
@Override
3409-
public boolean upgradeVmDb(final long vmId, final long serviceOfferingId) {
3410-
final VMInstanceVO vmForUpdate = _vmDao.createForUpdate();
3411-
vmForUpdate.setServiceOfferingId(serviceOfferingId);
3412-
final ServiceOffering newSvcOff = _entityMgr.findById(ServiceOffering.class, serviceOfferingId);
3412+
public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) {
3413+
3414+
final VMInstanceVO vmForUpdate = _vmDao.findById(vmId);
3415+
vmForUpdate.setServiceOfferingId(newServiceOffering.getId());
3416+
final ServiceOffering newSvcOff = _entityMgr.findById(ServiceOffering.class, newServiceOffering.getId());
34133417
vmForUpdate.setHaEnabled(newSvcOff.isOfferHA());
34143418
vmForUpdate.setLimitCpuUse(newSvcOff.getLimitCpuUse());
34153419
vmForUpdate.setServiceOfferingId(newSvcOff.getId());
3420+
if (newServiceOffering.isDynamic()) {
3421+
saveCustomOfferingDetails(vmId, newServiceOffering);
3422+
}
3423+
if (currentServiceOffering.isDynamic() && !newServiceOffering.isDynamic()) {
3424+
removeCustomOfferingDetails(vmId);
3425+
}
34163426
return _vmDao.update(vmId, vmForUpdate);
34173427
}
34183428

@@ -4087,8 +4097,8 @@ public boolean unplugNic(final Network network, final NicTO nic, final VirtualMa
40874097
}
40884098

40894099
@Override
4090-
public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering,
4091-
final boolean reconfiguringOnExistingHost)
4100+
public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering,
4101+
Map<String, String> customParameters, final boolean reconfiguringOnExistingHost)
40924102
throws ResourceUnavailableException, InsufficientServerCapacityException, ConcurrentOperationException {
40934103

40944104
final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
@@ -4098,14 +4108,14 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old
40984108
final VirtualMachine vm = _vmDao.findByUuid(vmUuid);
40994109
placeHolder = createPlaceHolderWork(vm.getId());
41004110
try {
4101-
return orchestrateReConfigureVm(vmUuid, oldServiceOffering, reconfiguringOnExistingHost);
4111+
return orchestrateReConfigureVm(vmUuid, oldServiceOffering, newServiceOffering, reconfiguringOnExistingHost);
41024112
} finally {
41034113
if (placeHolder != null) {
41044114
_workJobDao.expunge(placeHolder.getId());
41054115
}
41064116
}
41074117
} else {
4108-
final Outcome<VirtualMachine> outcome = reconfigureVmThroughJobQueue(vmUuid, oldServiceOffering, reconfiguringOnExistingHost);
4118+
final Outcome<VirtualMachine> outcome = reconfigureVmThroughJobQueue(vmUuid, oldServiceOffering, newServiceOffering, customParameters, reconfiguringOnExistingHost);
41094119

41104120
VirtualMachine vm = null;
41114121
try {
@@ -4134,14 +4144,12 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old
41344144
}
41354145
}
41364146

4137-
private VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final boolean reconfiguringOnExistingHost) throws ResourceUnavailableException,
4138-
ConcurrentOperationException {
4147+
private VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering,
4148+
final boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException {
41394149
final VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
4150+
upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering);
41404151

4141-
final long newServiceofferingId = vm.getServiceOfferingId();
4142-
final ServiceOffering newServiceOffering = _offeringDao.findById(vm.getId(), newServiceofferingId);
41434152
final HostVO hostVo = _hostDao.findById(vm.getHostId());
4144-
41454153
final Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(hostVo.getClusterId());
41464154
final Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(hostVo.getClusterId());
41474155
final long minMemory = (long)(newServiceOffering.getRamSize() / memoryOvercommitRatio);
@@ -4168,7 +4176,7 @@ private VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final Service
41684176
if (reconfiguringOnExistingHost) {
41694177
vm.setServiceOfferingId(oldServiceOffering.getId());
41704178
_capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); //release the old capacity
4171-
vm.setServiceOfferingId(newServiceofferingId);
4179+
vm.setServiceOfferingId(newServiceOffering.getId());
41724180
_capacityMgr.allocateVmCapacity(vm, false); // lock the new capacity
41734181
}
41744182

@@ -4177,7 +4185,9 @@ private VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final Service
41774185
s_logger.error("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails()));
41784186
throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails()));
41794187
}
4180-
4188+
if (vm.getType().equals(VirtualMachine.Type.User)) {
4189+
_userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE);
4190+
}
41814191
success = true;
41824192
} catch (final OperationTimedoutException e) {
41834193
throw new AgentUnavailableException("Operation timed out on reconfiguring " + vm, dstHostId);
@@ -4186,7 +4196,7 @@ private VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final Service
41864196
} finally {
41874197
if (!success) {
41884198
_capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); // release the new capacity
4189-
vm.setServiceOfferingId(oldServiceOffering.getId());
4199+
upgradeVmDb(vm.getId(), oldServiceOffering, newServiceOffering); // rollback
41904200
_capacityMgr.allocateVmCapacity(vm, false); // allocate the old capacity
41914201
}
41924202
}
@@ -4195,6 +4205,33 @@ private VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final Service
41954205

41964206
}
41974207

4208+
private void removeCustomOfferingDetails(long vmId) {
4209+
Map<String, String> details = userVmDetailsDao.listDetailsKeyPairs(vmId);
4210+
details.remove(UsageEventVO.DynamicParameters.cpuNumber.name());
4211+
details.remove(UsageEventVO.DynamicParameters.cpuSpeed.name());
4212+
details.remove(UsageEventVO.DynamicParameters.memory.name());
4213+
List<UserVmDetailVO> detailList = new ArrayList<UserVmDetailVO>();
4214+
for(Map.Entry<String, String> entry: details.entrySet()) {
4215+
UserVmDetailVO detailVO = new UserVmDetailVO(vmId, entry.getKey(), entry.getValue(), true);
4216+
detailList.add(detailVO);
4217+
}
4218+
userVmDetailsDao.saveDetails(detailList);
4219+
}
4220+
4221+
private void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering) {
4222+
//save the custom values to the database.
4223+
Map<String, String> details = userVmDetailsDao.listDetailsKeyPairs(vmId);
4224+
details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), serviceOffering.getCpu().toString());
4225+
details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), serviceOffering.getSpeed().toString());
4226+
details.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString());
4227+
List<UserVmDetailVO> detailList = new ArrayList<UserVmDetailVO>();
4228+
for (Map.Entry<String, String> entry: details.entrySet()) {
4229+
UserVmDetailVO detailVO = new UserVmDetailVO(vmId, entry.getKey(), entry.getValue(), true);
4230+
detailList.add(detailVO);
4231+
}
4232+
userVmDetailsDao.saveDetails(detailList);
4233+
}
4234+
41984235
@Override
41994236
public String getConfigComponentName() {
42004237
return VirtualMachineManager.class.getSimpleName();
@@ -5090,7 +5127,7 @@ public Outcome<VirtualMachine> removeVmFromNetworkThroughJobQueue(
50905127
}
50915128

50925129
public Outcome<VirtualMachine> reconfigureVmThroughJobQueue(
5093-
final String vmUuid, final ServiceOffering newServiceOffering, final boolean reconfiguringOnExistingHost) {
5130+
final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, Map<String, String> customParameters, final boolean reconfiguringOnExistingHost) {
50945131

50955132
final CallContext context = CallContext.current();
50965133
final User user = context.getCallingUser();
@@ -5121,7 +5158,7 @@ public Outcome<VirtualMachine> reconfigureVmThroughJobQueue(
51215158

51225159
// save work context info (there are some duplications)
51235160
final VmWorkReconfigure workInfo = new VmWorkReconfigure(user.getId(), account.getId(), vm.getId(),
5124-
VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER, newServiceOffering.getId(), reconfiguringOnExistingHost);
5161+
VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER, oldServiceOffering.getId(), newServiceOffering.getId(), customParameters, reconfiguringOnExistingHost);
51255162
workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
51265163

51275164
_jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
@@ -5280,10 +5317,14 @@ private Pair<JobInfo.Status, String> orchestrateReconfigure(final VmWorkReconfig
52805317
s_logger.info("Unable to find vm " + work.getVmId());
52815318
}
52825319
assert vm != null;
5320+
ServiceOfferingVO oldServiceOffering = _offeringDao.findById(work.getOldServiceOfferingId());
5321+
ServiceOfferingVO newServiceOffering = _offeringDao.findById(work.getNewServiceOfferingId());
5322+
if (newServiceOffering.isDynamic()) {
5323+
// update the service offering object with the custom parameters like cpu, memory
5324+
newServiceOffering = _offeringDao.getcomputeOffering(newServiceOffering, work.getCustomParameters());
5325+
}
52835326

5284-
final ServiceOffering newServiceOffering = _offeringDao.findById(vm.getId(), work.getNewServiceOfferingId());
5285-
5286-
reConfigureVm(vm.getUuid(), newServiceOffering,
5327+
reConfigureVm(vm.getUuid(), oldServiceOffering, newServiceOffering, work.getCustomParameters(),
52875328
work.isSameHost());
52885329
return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, null);
52895330
}

engine/orchestration/src/main/java/com/cloud/vm/VmWorkReconfigure.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,40 @@
1717
package com.cloud.vm;
1818

1919

20+
import java.util.Map;
21+
2022
public class VmWorkReconfigure extends VmWork {
2123
private static final long serialVersionUID = -4517030323758086615L;
2224

25+
Long oldServiceOfferingId;
2326
Long newServiceOfferingId;
27+
28+
Map<String, String> customParameters;
2429
boolean sameHost;
2530

26-
public VmWorkReconfigure(long userId, long accountId, long vmId, String handlerName,
27-
Long newServiceOfferingId, boolean sameHost) {
31+
public VmWorkReconfigure(long userId, long accountId, long vmId, String handlerName, Long oldServiceOfferingId,
32+
Long newServiceOfferingId, Map<String, String> customParameters, boolean sameHost) {
2833

2934
super(userId, accountId, vmId, handlerName);
3035

36+
this.oldServiceOfferingId = oldServiceOfferingId;
3137
this.newServiceOfferingId = newServiceOfferingId;
38+
this.customParameters = customParameters;
3239
this.sameHost = sameHost;
3340
}
3441

42+
public Long getOldServiceOfferingId() {
43+
return oldServiceOfferingId;
44+
}
45+
3546
public Long getNewServiceOfferingId() {
3647
return newServiceOfferingId;
3748
}
3849

50+
public Map<String, String> getCustomParameters() {
51+
return customParameters;
52+
}
53+
3954
public boolean isSameHost() {
4055
return sameHost;
4156
}

server/src/main/java/com/cloud/server/ManagementServerImpl.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4086,15 +4086,7 @@ private VirtualMachine upgradeStoppedSystemVm(final Long systemVmId, final Long
40864086
}
40874087
_itMgr.checkIfCanUpgrade(systemVm, newServiceOffering);
40884088

4089-
final boolean result = _itMgr.upgradeVmDb(systemVmId, serviceOfferingId);
4090-
4091-
if (newServiceOffering.isDynamic()) {
4092-
//save the custom values to the database.
4093-
_userVmMgr.saveCustomOfferingDetails(systemVmId, newServiceOffering);
4094-
}
4095-
if (currentServiceOffering.isDynamic() && !newServiceOffering.isDynamic()) {
4096-
_userVmMgr.removeCustomOfferingDetails(systemVmId);
4097-
}
4089+
final boolean result = _itMgr.upgradeVmDb(systemVmId, newServiceOffering, currentServiceOffering);
40984090

40994091
if (result) {
41004092
return _vmInstanceDao.findById(systemVmId);

server/src/main/java/com/cloud/vm/UserVmManager.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import com.cloud.exception.ManagementServerException;
3333
import com.cloud.exception.ResourceUnavailableException;
3434
import com.cloud.exception.VirtualMachineMigrationException;
35-
import com.cloud.offering.ServiceOffering;
3635
import com.cloud.service.ServiceOfferingVO;
3736
import com.cloud.storage.Storage.StoragePoolType;
3837
import com.cloud.user.Account;
@@ -117,10 +116,6 @@ UserVm updateVirtualMachine(long id, String displayName, String group, Boolean h
117116
//find a common place for all the scaling and upgrading code of both user and systemvms.
118117
void validateCustomParameters(ServiceOfferingVO serviceOffering, Map<String, String> customParameters);
119118

120-
public void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering);
121-
122-
public void removeCustomOfferingDetails(long vmId);
123-
124119
void generateUsageEvent(VirtualMachine vm, boolean isDisplay, String eventType);
125120

126121
void persistDeviceBusInfo(UserVmVO paramUserVmVO, String paramString);

0 commit comments

Comments
 (0)