Skip to content

Commit 8a16729

Browse files
GutoVeroneziDaniel Augusto Veronezi Salvador
andauthored
Support vm dynamic scaling with kvm (#4878)
* Create utility to centralize byte convertions * Add/change toString definitions * Create Libvirt handler to ScaleVmCommand * Enable dynamic scalling VM with KVM * Move config from interface to class and rename it As every variable declared in interfaces are already final, this moving will be needed to mock tests in nexts commits * Configure VM max memory and cpu cores The values are according to service offering or global configs * Extract dpdk configuration to a method and test it * Extract OS desc config to a method and test it * Extract guest resource def to a method and test it Improve libvirt def * Refactor LibvirtVMDef.GuestResourceDef * Refactor ScaleVmCommand * Improve VMInstaVO toString() * Refactor upgradeRunningVirtualMachine method * Turn int variables into long on utility * Verify if VM is scalable on KVMGuru * Rename some KVMGuruTest's methods * Change vm's xml to work with max memory * Verify if service offering is dynamic before scale * Create methods to retrieve data from domain * Create def to hotplug memory * Adjust the way command was scaling the VM * Fix database persistence before executing command * Send more info to host to improve log * Fix var name * Fix missing "}" * Undo unnecessary changes * Address review * Fix scale validation * Add VM prepared for dynamic scaling validation * Refactor LibvirtScaleVmCommandWrapper and improve unit tests * Remove duplicated method * Add RuntimeException check * Remove copyright from header * Remove copyright from header * Remove copyright from header * Remove copyright from header * Remove copyright from header * Update ByteScaleUtilsTest.java Co-authored-by: Daniel Augusto Veronezi Salvador <daniel@scclouds.com.br>
1 parent 9c51009 commit 8a16729

File tree

20 files changed

+1261
-132
lines changed

20 files changed

+1261
-132
lines changed

api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ public Type getType() {
146146
return type;
147147
}
148148

149+
public void setType(Type type) {
150+
this.type = type;
151+
}
152+
149153
public BootloaderType getBootloader() {
150154
return bootloader;
151155
}

core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpee
5252
this.minRam = minRam;
5353
this.maxRam = maxRam;
5454
this.vm = new VirtualMachineTO(1L, vmName, null, cpus, minSpeed, maxSpeed, minRam, maxRam, null, null, false, limitCpuUse, null);
55-
/*vm.setName(vmName);
56-
vm.setCpus(cpus);
57-
vm.setRam(minRam, maxRam);*/
5855
}
5956

6057
public void setCpus(int cpus) {

core/src/main/java/com/cloud/resource/CommandWrapper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121

2222
import com.cloud.agent.api.Answer;
2323
import com.cloud.agent.api.Command;
24+
import org.apache.log4j.Logger;
2425

2526
public abstract class CommandWrapper<T extends Command, A extends Answer, R extends ServerResource> {
27+
protected Logger logger = Logger.getLogger(getClass());
2628

2729
/**
2830
* @param T is the command to be used.

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,6 @@ public interface VirtualMachineManager extends Manager {
7676
ConfigKey<Boolean> AllowExposeHypervisorHostname = new ConfigKey<Boolean>("Advanced", Boolean.class, "global.allow.expose.host.hostname",
7777
"false", "If set to true, it allows the hypervisor host name on which the VM is spawned on to be exposed to the VM", true, ConfigKey.Scope.Global);
7878

79-
static final ConfigKey<Integer> VmServiceOfferingMaxCPUCores = new ConfigKey<Integer>("Advanced",
80-
Integer.class,
81-
"vm.serviceoffering.cpu.cores.max",
82-
"0",
83-
"Maximum CPU cores for vm service offering. If 0 - no limitation",
84-
true
85-
);
86-
87-
static final ConfigKey<Integer> VmServiceOfferingMaxRAMSize = new ConfigKey<Integer>("Advanced",
88-
Integer.class,
89-
"vm.serviceoffering.ram.size.max",
90-
"0",
91-
"Maximum RAM size in MB for vm service offering. If 0 - no limitation",
92-
true
93-
);
94-
9579
interface Topics {
9680
String VM_POWER_STATE = "vm.powerstate";
9781
}

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

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4665,6 +4665,8 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old
46654665
throw (ConcurrentOperationException)jobResult;
46664666
} else if (jobResult instanceof InsufficientServerCapacityException) {
46674667
throw (InsufficientServerCapacityException)jobResult;
4668+
} else if (jobResult instanceof RuntimeException) {
4669+
throw (RuntimeException)jobResult;
46684670
} else if (jobResult instanceof Throwable) {
46694671
s_logger.error("Unhandled exception", (Throwable)jobResult);
46704672
throw new RuntimeException("Unhandled exception", (Throwable)jobResult);
@@ -4677,8 +4679,7 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old
46774679

46784680
private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering,
46794681
boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException {
4680-
VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
4681-
upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering);
4682+
final VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
46824683

46834684
HostVO hostVo = _hostDao.findById(vm.getHostId());
46844685

@@ -4695,6 +4696,10 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old
46954696
new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed,
46964697
newServiceOffering.getSpeed(), minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, newServiceOffering.getLimitCpuUse());
46974698

4699+
scaleVmCommand.getVirtualMachine().setId(vm.getId());
4700+
scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid());
4701+
scaleVmCommand.getVirtualMachine().setType(vm.getType());
4702+
46984703
Long dstHostId = vm.getHostId();
46994704

47004705
if (vm.getHypervisorType().equals(HypervisorType.VMware)) {
@@ -4710,36 +4715,31 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old
47104715
work.setResourceId(vm.getHostId());
47114716
_workDao.persist(work);
47124717

4713-
boolean success = false;
4714-
47154718
try {
4719+
Answer reconfigureAnswer = _agentMgr.send(vm.getHostId(), scaleVmCommand);
4720+
4721+
if (reconfigureAnswer == null || !reconfigureAnswer.getResult()) {
4722+
s_logger.error("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails()));
4723+
throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails()));
4724+
}
4725+
4726+
if (vm.getType().equals(VirtualMachine.Type.User)) {
4727+
_userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE);
4728+
}
4729+
4730+
upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering);
4731+
47164732
if (reconfiguringOnExistingHost) {
47174733
vm.setServiceOfferingId(oldServiceOffering.getId());
47184734
_capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); //release the old capacity
47194735
vm.setServiceOfferingId(newServiceOffering.getId());
47204736
_capacityMgr.allocateVmCapacity(vm, false); // lock the new capacity
47214737
}
47224738

4723-
Answer scaleVmAnswer = _agentMgr.send(vm.getHostId(), scaleVmCommand);
4724-
if (scaleVmAnswer == null || !scaleVmAnswer.getResult()) {
4725-
String msg = String.format("Unable to scale %s due to [%s].", vm.toString(), (scaleVmAnswer == null ? "" : scaleVmAnswer.getDetails()));
4726-
s_logger.error(msg);
4727-
throw new CloudRuntimeException(msg);
4728-
}
4729-
if (vm.getType().equals(VirtualMachine.Type.User)) {
4730-
_userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE);
4731-
}
4732-
success = true;
4733-
} catch (OperationTimedoutException e) {
4734-
throw new AgentUnavailableException(String.format("Unable to scale %s due to [%s].", vm.toString(), e.getMessage()), dstHostId, e);
4739+
} catch (final OperationTimedoutException e) {
4740+
throw new AgentUnavailableException("Operation timed out on reconfiguring " + vm, dstHostId);
47354741
} catch (final AgentUnavailableException e) {
47364742
throw e;
4737-
} finally {
4738-
if (!success) {
4739-
_capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); // release the new capacity
4740-
upgradeVmDb(vm.getId(), oldServiceOffering, newServiceOffering); // rollback
4741-
_capacityMgr.allocateVmCapacity(vm, false); // allocate the old capacity
4742-
}
47434743
}
47444744

47454745
return vm;
@@ -4783,8 +4783,7 @@ public ConfigKey<?>[] getConfigKeys() {
47834783
return new ConfigKey<?>[] { ClusterDeltaSyncInterval, StartRetry, VmDestroyForcestop, VmOpCancelInterval, VmOpCleanupInterval, VmOpCleanupWait,
47844784
VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval,
47854785
VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool,
4786-
HaVmRestartHostUp, ResoureCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel,
4787-
VmServiceOfferingMaxCPUCores, VmServiceOfferingMaxRAMSize };
4786+
HaVmRestartHostUp, ResoureCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel };
47884787
}
47894788

47904789
public List<StoragePoolAllocator> getStoragePoolAllocators() {

engine/schema/src/main/java/com/cloud/host/HostVO.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ public boolean equals(Object obj) {
680680

681681
@Override
682682
public String toString() {
683-
return String.format("Host [{id: \"%s\", name: \"%s\", uuid: \"%s\", type=\"%s\"}]", id, name, uuid, type);
683+
return String.format("Host {\"id\": \"%s\", \"name\": \"%s\", \"uuid\": \"%s\", \"type\"=\"%s\"}", id, name, uuid, type);
684684
}
685685

686686
public void setHypervisorType(HypervisorType hypervisorType) {

engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ public boolean isCustomCpuSpeedSupported() {
298298
}
299299

300300
@Override
301+
public String toString() {
302+
return String.format("Service offering {\"id\": %s, \"name\": \"%s\", \"uuid\": \"%s\"}", getId(), getName(), getUuid());
303+
}
304+
301305
public boolean isDynamicScalingEnabled() {
302306
return dynamicScalingEnabled;
303307
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@
185185
import com.cloud.vm.VirtualMachine.PowerState;
186186
import com.cloud.vm.VmDetailConstants;
187187
import com.google.common.base.Strings;
188+
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
189+
import org.libvirt.VcpuInfo;
188190

189191
/**
190192
* LibvirtComputingResource execute requests on the computing/routing host using
@@ -2534,21 +2536,6 @@ private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) {
25342536
return cmd;
25352537
}
25362538

2537-
/**
2538-
* Creates guest resources based in VM specification.
2539-
*/
2540-
protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO) {
2541-
GuestResourceDef grd = new GuestResourceDef();
2542-
2543-
grd.setMemorySize(vmTO.getMaxRam() / 1024);
2544-
if (vmTO.getMinRam() != vmTO.getMaxRam() && !_noMemBalloon) {
2545-
grd.setMemBalloning(true);
2546-
grd.setCurrentMem(vmTO.getMinRam() / 1024);
2547-
}
2548-
grd.setVcpuNum(vmTO.getCpus());
2549-
return grd;
2550-
}
2551-
25522539
private void configureGuestIfUefiEnabled(boolean isSecureBoot, String bootMode, GuestDef guest) {
25532540
setGuestLoader(bootMode, SECURE, guest, GuestDef.GUEST_LOADER_SECURE);
25542541
setGuestLoader(bootMode, LEGACY, guest, GuestDef.GUEST_LOADER_LEGACY);
@@ -2628,6 +2615,37 @@ private void configureGuestAndUserVMToUseLXC(LibvirtVMDef vm, GuestDef guest) {
26282615
vm.setHvsType(HypervisorType.LXC.toString().toLowerCase());
26292616
}
26302617

2618+
/**
2619+
* Creates guest resources based in VM specification.
2620+
*/
2621+
protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO){
2622+
GuestResourceDef grd = new GuestResourceDef();
2623+
2624+
grd.setMemBalloning(!_noMemBalloon);
2625+
2626+
Long maxRam = ByteScaleUtils.bytesToKib(vmTO.getMaxRam());
2627+
2628+
grd.setMemorySize(maxRam);
2629+
grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, maxRam));
2630+
2631+
int vcpus = vmTO.getCpus();
2632+
Integer maxVcpus = vmTO.getVcpuMaxLimit();
2633+
2634+
grd.setVcpuNum(vcpus);
2635+
grd.setMaxVcpuNum(maxVcpus == null ? vcpus : maxVcpus);
2636+
2637+
return grd;
2638+
}
2639+
2640+
protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long maxRam) {
2641+
if (_noMemBalloon) {
2642+
s_logger.warn(String.format("Setting VM's [%s] current memory as max memory [%s] due to memory ballooning is disabled. If you are using a custom service offering, verify if memory ballooning really should be disabled.", vmTO.toString(), maxRam));
2643+
return maxRam;
2644+
} else {
2645+
return ByteScaleUtils.bytesToKib(vmTO.getMinRam());
2646+
}
2647+
}
2648+
26312649
/**
26322650
* Adds extra configurations (if any) as a String component to the domain XML
26332651
*/
@@ -4556,4 +4574,25 @@ public void setBackingFileFormat(String volPath) {
45564574
}
45574575
}
45584576

4577+
/**
4578+
* Retrieves the memory of the running VM. <br/>
4579+
* The libvirt (see <a href="https://github.com/libvirt/libvirt/blob/master/src/conf/domain_conf.c">https://github.com/libvirt/libvirt/blob/master/src/conf/domain_conf.c</a>, function <b>virDomainDefParseMemory</b>) uses <b>total memory</b> as the tag <b>memory</b>, in VM's XML.
4580+
* @param dm domain of the VM.
4581+
* @return the memory of the VM.
4582+
* @throws org.libvirt.LibvirtException
4583+
**/
4584+
public static long getDomainMemory(Domain dm) throws LibvirtException {
4585+
return dm.getMaxMemory();
4586+
}
4587+
4588+
/**
4589+
* Retrieves the quantity of running VCPUs of the running VM. <br/>
4590+
* @param dm domain of the VM.
4591+
* @return the quantity of running VCPUs of the running VM.
4592+
* @throws org.libvirt.LibvirtException
4593+
**/
4594+
public static long countDomainRunningVcpus(Domain dm) throws LibvirtException {
4595+
VcpuInfo vcpus[] = dm.getVcpusInfo();
4596+
return Arrays.stream(vcpus).filter(vcpu -> vcpu.state.equals(VcpuInfo.VcpuState.VIR_VCPU_RUNNING)).count();
4597+
}
45594598
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -230,51 +230,54 @@ public String toString() {
230230
}
231231

232232
public static class GuestResourceDef {
233-
private long _mem;
234-
private long _currentMem = -1;
235-
private String _memBacking;
236-
private int _vcpu = -1;
237-
private boolean _memBalloning = false;
233+
private long memory;
234+
private long currentMemory = -1;
235+
private int vcpu = -1;
236+
private int maxVcpu = -1;
237+
private boolean memoryBalloning = false;
238238

239239
public void setMemorySize(long mem) {
240-
_mem = mem;
240+
this.memory = mem;
241241
}
242242

243243
public void setCurrentMem(long currMem) {
244-
_currentMem = currMem;
244+
this.currentMemory = currMem;
245245
}
246246

247-
public void setMemBacking(String memBacking) {
248-
_memBacking = memBacking;
247+
public void setVcpuNum(int vcpu) {
248+
this.vcpu = vcpu;
249249
}
250250

251-
public void setVcpuNum(int vcpu) {
252-
_vcpu = vcpu;
251+
public void setMaxVcpuNum(int maxVcpu) {
252+
this.maxVcpu = maxVcpu;
253+
}
254+
255+
public int getVcpu() {
256+
return vcpu;
257+
}
258+
259+
public int getMaxVcpu() {
260+
return maxVcpu;
253261
}
254262

255-
public void setMemBalloning(boolean turnon) {
256-
_memBalloning = turnon;
263+
public void setMemBalloning(boolean memoryBalloning) {
264+
this.memoryBalloning = memoryBalloning;
257265
}
258266

259267
@Override
260268
public String toString() {
261-
StringBuilder resBuidler = new StringBuilder();
262-
resBuidler.append("<memory>" + _mem + "</memory>\n");
263-
if (_currentMem != -1) {
264-
resBuidler.append("<currentMemory>" + _currentMem + "</currentMemory>\n");
265-
}
266-
if (_memBacking != null) {
267-
resBuidler.append("<memoryBacking>" + "<" + _memBacking + "/>" + "</memoryBacking>\n");
268-
}
269-
if (_memBalloning) {
270-
resBuidler.append("<devices>\n" + "<memballoon model='virtio'/>\n" + "</devices>\n");
271-
} else {
272-
resBuidler.append("<devices>\n" + "<memballoon model='none'/>\n" + "</devices>\n");
273-
}
274-
if (_vcpu != -1) {
275-
resBuidler.append("<vcpu>" + _vcpu + "</vcpu>\n");
269+
StringBuilder response = new StringBuilder();
270+
response.append(String.format("<memory>%s</memory>\n", this.currentMemory));
271+
response.append(String.format("<currentMemory>%s</currentMemory>\n", this.currentMemory));
272+
273+
if (this.memory > this.currentMemory) {
274+
response.append(String.format("<maxMemory slots='16' unit='KiB'>%s</maxMemory>\n", this.memory));
275+
response.append(String.format("<cpu> <numa> <cell id='0' cpus='0-%s' memory='%s' unit='KiB'/> </numa> </cpu>\n", this.maxVcpu - 1, this.currentMemory));
276276
}
277-
return resBuidler.toString();
277+
278+
response.append(String.format("<devices>\n<memballoon model='%s'/>\n</devices>\n", this.memoryBalloning ? "virtio" : "none"));
279+
response.append(String.format("<vcpu current=\"%s\">%s</vcpu>\n", this.vcpu, this.maxVcpu));
280+
return response.toString();
278281
}
279282
}
280283

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.cloud.hypervisor.kvm.resource;
16+
17+
/**
18+
* Provides the XML definition to a memory device which can be hotpluged to the VM.<br/>
19+
* Memory is provided in KiB.
20+
*
21+
*/
22+
public class LibvirtVmMemoryDeviceDef {
23+
24+
private final long memorySize;
25+
26+
public LibvirtVmMemoryDeviceDef(long memorySize) {
27+
this.memorySize = memorySize;
28+
}
29+
30+
@Override
31+
public String toString() {
32+
StringBuilder response = new StringBuilder();
33+
response.append("<memory model='dimm'>");
34+
response.append("<target>");
35+
response.append(String.format("<size unit='KiB'>%s</size>", memorySize));
36+
response.append("<node>0</node>");
37+
response.append("</target>");
38+
response.append("</memory>");
39+
40+
return response.toString();
41+
}
42+
43+
}

0 commit comments

Comments
 (0)