diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java index 8a30b5ef9fee..5fc248343ecc 100644 --- a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java @@ -146,6 +146,10 @@ public Type getType() { return type; } + public void setType(Type type) { + this.type = type; + } + public BootloaderType getBootloader() { return bootloader; } diff --git a/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java b/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java index 1c763dd05dd9..dc63b1ee746d 100644 --- a/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java @@ -52,9 +52,6 @@ public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpee this.minRam = minRam; this.maxRam = maxRam; this.vm = new VirtualMachineTO(1L, vmName, null, cpus, minSpeed, maxSpeed, minRam, maxRam, null, null, false, limitCpuUse, null); - /*vm.setName(vmName); - vm.setCpus(cpus); - vm.setRam(minRam, maxRam);*/ } public void setCpus(int cpus) { diff --git a/core/src/main/java/com/cloud/resource/CommandWrapper.java b/core/src/main/java/com/cloud/resource/CommandWrapper.java index f8596998b18e..21457500808c 100644 --- a/core/src/main/java/com/cloud/resource/CommandWrapper.java +++ b/core/src/main/java/com/cloud/resource/CommandWrapper.java @@ -21,8 +21,10 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; +import org.apache.log4j.Logger; public abstract class CommandWrapper { + protected Logger logger = Logger.getLogger(getClass()); /** * @param T is the command to be used. diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index e7828fabef8d..68183ad2add1 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -76,22 +76,6 @@ public interface VirtualMachineManager extends Manager { ConfigKey AllowExposeHypervisorHostname = new ConfigKey("Advanced", Boolean.class, "global.allow.expose.host.hostname", "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); - static final ConfigKey VmServiceOfferingMaxCPUCores = new ConfigKey("Advanced", - Integer.class, - "vm.serviceoffering.cpu.cores.max", - "0", - "Maximum CPU cores for vm service offering. If 0 - no limitation", - true - ); - - static final ConfigKey VmServiceOfferingMaxRAMSize = new ConfigKey("Advanced", - Integer.class, - "vm.serviceoffering.ram.size.max", - "0", - "Maximum RAM size in MB for vm service offering. If 0 - no limitation", - true - ); - interface Topics { String VM_POWER_STATE = "vm.powerstate"; } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index eca0852060d8..1c77295a75a7 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -4665,6 +4665,8 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old throw (ConcurrentOperationException)jobResult; } else if (jobResult instanceof InsufficientServerCapacityException) { throw (InsufficientServerCapacityException)jobResult; + } else if (jobResult instanceof RuntimeException) { + throw (RuntimeException)jobResult; } else if (jobResult instanceof Throwable) { s_logger.error("Unhandled exception", (Throwable)jobResult); throw new RuntimeException("Unhandled exception", (Throwable)jobResult); @@ -4677,8 +4679,7 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException { - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); HostVO hostVo = _hostDao.findById(vm.getHostId()); @@ -4695,6 +4696,10 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, newServiceOffering.getSpeed(), minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, newServiceOffering.getLimitCpuUse()); + scaleVmCommand.getVirtualMachine().setId(vm.getId()); + scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid()); + scaleVmCommand.getVirtualMachine().setType(vm.getType()); + Long dstHostId = vm.getHostId(); if (vm.getHypervisorType().equals(HypervisorType.VMware)) { @@ -4710,9 +4715,20 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old work.setResourceId(vm.getHostId()); _workDao.persist(work); - boolean success = false; - try { + Answer reconfigureAnswer = _agentMgr.send(vm.getHostId(), scaleVmCommand); + + if (reconfigureAnswer == null || !reconfigureAnswer.getResult()) { + s_logger.error("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); + throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); + } + + if (vm.getType().equals(VirtualMachine.Type.User)) { + _userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE); + } + + upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + if (reconfiguringOnExistingHost) { vm.setServiceOfferingId(oldServiceOffering.getId()); _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); //release the old capacity @@ -4720,26 +4736,10 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old _capacityMgr.allocateVmCapacity(vm, false); // lock the new capacity } - Answer scaleVmAnswer = _agentMgr.send(vm.getHostId(), scaleVmCommand); - if (scaleVmAnswer == null || !scaleVmAnswer.getResult()) { - String msg = String.format("Unable to scale %s due to [%s].", vm.toString(), (scaleVmAnswer == null ? "" : scaleVmAnswer.getDetails())); - s_logger.error(msg); - throw new CloudRuntimeException(msg); - } - if (vm.getType().equals(VirtualMachine.Type.User)) { - _userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE); - } - success = true; - } catch (OperationTimedoutException e) { - throw new AgentUnavailableException(String.format("Unable to scale %s due to [%s].", vm.toString(), e.getMessage()), dstHostId, e); + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException("Operation timed out on reconfiguring " + vm, dstHostId); } catch (final AgentUnavailableException e) { throw e; - } finally { - if (!success) { - _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); // release the new capacity - upgradeVmDb(vm.getId(), oldServiceOffering, newServiceOffering); // rollback - _capacityMgr.allocateVmCapacity(vm, false); // allocate the old capacity - } } return vm; @@ -4783,8 +4783,7 @@ public ConfigKey[] getConfigKeys() { return new ConfigKey[] { ClusterDeltaSyncInterval, StartRetry, VmDestroyForcestop, VmOpCancelInterval, VmOpCleanupInterval, VmOpCleanupWait, VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, - HaVmRestartHostUp, ResoureCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, - VmServiceOfferingMaxCPUCores, VmServiceOfferingMaxRAMSize }; + HaVmRestartHostUp, ResoureCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel }; } public List getStoragePoolAllocators() { diff --git a/engine/schema/src/main/java/com/cloud/host/HostVO.java b/engine/schema/src/main/java/com/cloud/host/HostVO.java index 193131850b9e..776e48c2730c 100644 --- a/engine/schema/src/main/java/com/cloud/host/HostVO.java +++ b/engine/schema/src/main/java/com/cloud/host/HostVO.java @@ -680,7 +680,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return String.format("Host [{id: \"%s\", name: \"%s\", uuid: \"%s\", type=\"%s\"}]", id, name, uuid, type); + return String.format("Host {\"id\": \"%s\", \"name\": \"%s\", \"uuid\": \"%s\", \"type\"=\"%s\"}", id, name, uuid, type); } public void setHypervisorType(HypervisorType hypervisorType) { diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index 44f39a1050ba..a23623164208 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -298,6 +298,10 @@ public boolean isCustomCpuSpeedSupported() { } @Override + public String toString() { + return String.format("Service offering {\"id\": %s, \"name\": \"%s\", \"uuid\": \"%s\"}", getId(), getName(), getUuid()); + } + public boolean isDynamicScalingEnabled() { return dynamicScalingEnabled; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index df729cbe355b..239430757737 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -185,6 +185,8 @@ import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VmDetailConstants; import com.google.common.base.Strings; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.libvirt.VcpuInfo; /** * LibvirtComputingResource execute requests on the computing/routing host using @@ -2534,21 +2536,6 @@ private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) { return cmd; } - /** - * Creates guest resources based in VM specification. - */ - protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO) { - GuestResourceDef grd = new GuestResourceDef(); - - grd.setMemorySize(vmTO.getMaxRam() / 1024); - if (vmTO.getMinRam() != vmTO.getMaxRam() && !_noMemBalloon) { - grd.setMemBalloning(true); - grd.setCurrentMem(vmTO.getMinRam() / 1024); - } - grd.setVcpuNum(vmTO.getCpus()); - return grd; - } - private void configureGuestIfUefiEnabled(boolean isSecureBoot, String bootMode, GuestDef guest) { setGuestLoader(bootMode, SECURE, guest, GuestDef.GUEST_LOADER_SECURE); setGuestLoader(bootMode, LEGACY, guest, GuestDef.GUEST_LOADER_LEGACY); @@ -2628,6 +2615,37 @@ private void configureGuestAndUserVMToUseLXC(LibvirtVMDef vm, GuestDef guest) { vm.setHvsType(HypervisorType.LXC.toString().toLowerCase()); } + /** + * Creates guest resources based in VM specification. + */ + protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO){ + GuestResourceDef grd = new GuestResourceDef(); + + grd.setMemBalloning(!_noMemBalloon); + + Long maxRam = ByteScaleUtils.bytesToKib(vmTO.getMaxRam()); + + grd.setMemorySize(maxRam); + grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, maxRam)); + + int vcpus = vmTO.getCpus(); + Integer maxVcpus = vmTO.getVcpuMaxLimit(); + + grd.setVcpuNum(vcpus); + grd.setMaxVcpuNum(maxVcpus == null ? vcpus : maxVcpus); + + return grd; + } + + protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long maxRam) { + if (_noMemBalloon) { + 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)); + return maxRam; + } else { + return ByteScaleUtils.bytesToKib(vmTO.getMinRam()); + } + } + /** * Adds extra configurations (if any) as a String component to the domain XML */ @@ -4556,4 +4574,25 @@ public void setBackingFileFormat(String volPath) { } } + /** + * Retrieves the memory of the running VM.
+ * The libvirt (see https://github.com/libvirt/libvirt/blob/master/src/conf/domain_conf.c, function virDomainDefParseMemory) uses total memory as the tag memory, in VM's XML. + * @param dm domain of the VM. + * @return the memory of the VM. + * @throws org.libvirt.LibvirtException + **/ + public static long getDomainMemory(Domain dm) throws LibvirtException { + return dm.getMaxMemory(); + } + + /** + * Retrieves the quantity of running VCPUs of the running VM.
+ * @param dm domain of the VM. + * @return the quantity of running VCPUs of the running VM. + * @throws org.libvirt.LibvirtException + **/ + public static long countDomainRunningVcpus(Domain dm) throws LibvirtException { + VcpuInfo vcpus[] = dm.getVcpusInfo(); + return Arrays.stream(vcpus).filter(vcpu -> vcpu.state.equals(VcpuInfo.VcpuState.VIR_VCPU_RUNNING)).count(); + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index c762cbff48a2..78d174445234 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -230,51 +230,54 @@ public String toString() { } public static class GuestResourceDef { - private long _mem; - private long _currentMem = -1; - private String _memBacking; - private int _vcpu = -1; - private boolean _memBalloning = false; + private long memory; + private long currentMemory = -1; + private int vcpu = -1; + private int maxVcpu = -1; + private boolean memoryBalloning = false; public void setMemorySize(long mem) { - _mem = mem; + this.memory = mem; } public void setCurrentMem(long currMem) { - _currentMem = currMem; + this.currentMemory = currMem; } - public void setMemBacking(String memBacking) { - _memBacking = memBacking; + public void setVcpuNum(int vcpu) { + this.vcpu = vcpu; } - public void setVcpuNum(int vcpu) { - _vcpu = vcpu; + public void setMaxVcpuNum(int maxVcpu) { + this.maxVcpu = maxVcpu; + } + + public int getVcpu() { + return vcpu; + } + + public int getMaxVcpu() { + return maxVcpu; } - public void setMemBalloning(boolean turnon) { - _memBalloning = turnon; + public void setMemBalloning(boolean memoryBalloning) { + this.memoryBalloning = memoryBalloning; } @Override public String toString() { - StringBuilder resBuidler = new StringBuilder(); - resBuidler.append("" + _mem + "\n"); - if (_currentMem != -1) { - resBuidler.append("" + _currentMem + "\n"); - } - if (_memBacking != null) { - resBuidler.append("" + "<" + _memBacking + "/>" + "\n"); - } - if (_memBalloning) { - resBuidler.append("\n" + "\n" + "\n"); - } else { - resBuidler.append("\n" + "\n" + "\n"); - } - if (_vcpu != -1) { - resBuidler.append("" + _vcpu + "\n"); + StringBuilder response = new StringBuilder(); + response.append(String.format("%s\n", this.currentMemory)); + response.append(String.format("%s\n", this.currentMemory)); + + if (this.memory > this.currentMemory) { + response.append(String.format("%s\n", this.memory)); + response.append(String.format(" \n", this.maxVcpu - 1, this.currentMemory)); } - return resBuidler.toString(); + + response.append(String.format("\n\n\n", this.memoryBalloning ? "virtio" : "none")); + response.append(String.format("%s\n", this.vcpu, this.maxVcpu)); + return response.toString(); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVmMemoryDeviceDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVmMemoryDeviceDef.java new file mode 100644 index 000000000000..1865a6d41d5a --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVmMemoryDeviceDef.java @@ -0,0 +1,43 @@ +/* + * Licensed 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.hypervisor.kvm.resource; + +/** + * Provides the XML definition to a memory device which can be hotpluged to the VM.
+ * Memory is provided in KiB. + * + */ +public class LibvirtVmMemoryDeviceDef { + + private final long memorySize; + + public LibvirtVmMemoryDeviceDef(long memorySize) { + this.memorySize = memorySize; + } + + @Override + public String toString() { + StringBuilder response = new StringBuilder(); + response.append(""); + response.append(""); + response.append(String.format("%s", memorySize)); + response.append("0"); + response.append(""); + response.append(""); + + return response.toString(); + } + +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java new file mode 100644 index 000000000000..384d5cc8b151 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java @@ -0,0 +1,103 @@ +/* + * Licensed 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.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ScaleVmAnswer; +import com.cloud.agent.api.ScaleVmCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.resource.LibvirtVmMemoryDeviceDef; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.LibvirtException; + +@ResourceWrapper(handles = ScaleVmCommand.class) +public class LibvirtScaleVmCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(ScaleVmCommand command, LibvirtComputingResource libvirtComputingResource) { + VirtualMachineTO vmSpec = command.getVirtualMachine(); + String vmName = vmSpec.getName(); + Connect conn = null; + + long newMemory = ByteScaleUtils.bytesToKib(vmSpec.getMaxRam()); + int newVcpus = vmSpec.getCpus(); + String vmDefinition = vmSpec.toString(); + String scalingDetails = String.format("%s memory to [%s KiB] and CPU cores to [%s]", vmDefinition, newMemory, newVcpus); + + try { + LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); + + conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName); + Domain dm = conn.domainLookupByName(vmName); + + logger.debug(String.format("Scaling %s.", scalingDetails)); + scaleMemory(dm, newMemory, vmDefinition); + scaleVcpus(dm, newVcpus, vmDefinition); + + return new ScaleVmAnswer(command, true, String.format("Successfully scaled %s.", scalingDetails)); + } catch (LibvirtException | CloudRuntimeException e) { + String message = String.format("Unable to scale %s due to [%s].", scalingDetails, e.getMessage()); + logger.error(message, e); + return new ScaleVmAnswer(command, false, message); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (LibvirtException ex) { + logger.warn(String.format("Error trying to close libvirt connection [%s]", ex.getMessage()), ex); + } + } + } + } + + protected void scaleVcpus(Domain dm, int newVcpus, String vmDefinition) throws LibvirtException { + long runningVcpus = LibvirtComputingResource.countDomainRunningVcpus(dm); + + if (runningVcpus < newVcpus) { + dm.setVcpus(newVcpus); + return; + } + + logger.info(String.format("Not scaling the CPU cores. To scale the CPU cores of the %s, the new CPU count [%s] must be higher than the current CPU count [%s].", + vmDefinition, newVcpus, runningVcpus)); + } + + protected void scaleMemory(Domain dm, long newMemory, String vmDefinition) throws LibvirtException, CloudRuntimeException { + long currentMemory = LibvirtComputingResource.getDomainMemory(dm); + long memoryToAttach = newMemory - currentMemory; + + if (memoryToAttach <= 0) { + logger.info(String.format("Not scaling the memory. To scale the memory of the %s, the new memory [%s] must be higher than the current memory [%s]. The current " + + "difference is [%s].", vmDefinition, newMemory, currentMemory, memoryToAttach)); + return; + } + + if (!dm.getXMLDesc(0).contains("")) { + throw new CloudRuntimeException(String.format("The %s is not prepared for dynamic scaling. To be prepared, the VM must be deployed with a dynamic service offering," + + " VM dynamic scale enabled and global setting \"enable.dynamic.scale.vm\" as \"true\". If you changed one of these settings after deploying the VM," + + " consider stopping and starting it again to prepared it to dynamic scaling.", vmDefinition)); + } + + String memoryDevice = new LibvirtVmMemoryDeviceDef(memoryToAttach).toString(); + logger.debug(String.format("Attaching memory device [%s] to %s.", memoryDevice, vmDefinition)); + dm.attachDevice(memoryDevice); + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index 86f30bf3bc3d..fbf1ec614ada 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -206,6 +206,8 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VirtualMachine.Type; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.libvirt.VcpuInfo; @RunWith(PowerMockRunner.class) @PrepareForTest(value = {MemStat.class}) @@ -221,6 +223,9 @@ public class LibvirtComputingResourceTest { @Spy private LibvirtComputingResource libvirtComputingResourceSpy = Mockito.spy(LibvirtComputingResource.class); + @Mock + Domain domainMock; + private final static long HYPERVISOR_LIBVIRT_VERSION_SUPPORTS_IOURING = 6003000; private final static long HYPERVISOR_QEMU_VERSION_SUPPORTS_IOURING = 5000000; @@ -328,6 +333,7 @@ public void testCreateVMFromSpecWithTopology4() { final VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, minSpeed, maxSpeed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); to.setVncAddr(vncAddr); to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); + to.setVcpuMaxLimit(cpus + 1); LibvirtVMDef vm = libvirtComputingResourceSpy.createVMFromSpec(to); vm.setHvsType(hyperVisorType); @@ -710,11 +716,15 @@ private void verifyMemballoonDevices(Document domainDoc) { } private void verifyVcpu(VirtualMachineTO to, Document domainDoc) { - assertXpath(domainDoc, "/domain/vcpu/text()", String.valueOf(to.getCpus())); + assertXpath(domainDoc, "/domain/cpu/numa/cell/@cpus", String.format("0-%s", to.getVcpuMaxLimit() - 1)); + assertXpath(domainDoc, "/domain/vcpu/@current", String.valueOf(to.getCpus())); + assertXpath(domainDoc, "/domain/vcpu/text()", String.valueOf(to.getVcpuMaxLimit())); } private void verifyMemory(VirtualMachineTO to, Document domainDoc, String minRam) { - assertXpath(domainDoc, "/domain/memory/text()", String.valueOf(to.getMaxRam() / 1024)); + assertXpath(domainDoc, "/domain/maxMemory/text()", String.valueOf( to.getMaxRam() / 1024 )); + assertXpath(domainDoc, "/domain/memory/text()",minRam); + assertXpath(domainDoc, "/domain/cpu/numa/cell/@memory", minRam); assertXpath(domainDoc, "/domain/currentMemory/text()", minRam); } @@ -5620,7 +5630,89 @@ public void testAddExtraConfigComponentNotEmptyExtraConfig() { Mockito.verify(vmDef, times(1)).addComp(any()); } + public void validateGetCurrentMemAccordingToMemBallooningWithoutMemBalooning(){ + VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); + LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); + libvirtComputingResource._noMemBalloon = true; + long maxMemory = 2048; + + long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory); + Assert.assertEquals(maxMemory, currentMemory); + Mockito.verify(vmTo, Mockito.times(0)).getMinRam(); + } + @Test + public void validateGetCurrentMemAccordingToMemBallooningWithtMemBalooning(){ + LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); + libvirtComputingResource._noMemBalloon = false; + + long maxMemory = 2048; + long minMemory = ByteScaleUtils.mibToBytes(64); + + VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); + Mockito.when(vmTo.getMinRam()).thenReturn(minMemory); + + long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory); + Assert.assertEquals(ByteScaleUtils.bytesToKib(minMemory), currentMemory); + Mockito.verify(vmTo).getMinRam(); + } + + @Test + public void validateCreateGuestResourceDefWithVcpuMaxLimit(){ + LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); + VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); + int maxCpu = 16; + + Mockito.when(vmTo.getVcpuMaxLimit()).thenReturn(maxCpu); + + LibvirtVMDef.GuestResourceDef grd = libvirtComputingResource.createGuestResourceDef(vmTo); + Assert.assertEquals(maxCpu, grd.getMaxVcpu()); + } + + @Test + public void validateCreateGuestResourceDefWithVcpuMaxLimitAsNull(){ + LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); + VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); + int min = 1; + + Mockito.when(vmTo.getCpus()).thenReturn(min); + Mockito.when(vmTo.getVcpuMaxLimit()).thenReturn(null); + + LibvirtVMDef.GuestResourceDef grd = libvirtComputingResource.createGuestResourceDef(vmTo); + Assert.assertEquals(min, grd.getMaxVcpu()); + } + + @Test + public void validateGetDomainMemory() throws LibvirtException{ + long valueExpected = ByteScaleUtils.KiB; + + Mockito.doReturn(valueExpected).when(domainMock).getMaxMemory(); + Assert.assertEquals(valueExpected, LibvirtComputingResource.getDomainMemory(domainMock)); + } + + private VcpuInfo createVcpuInfoWithState(VcpuInfo.VcpuState state) { + VcpuInfo vcpu = new VcpuInfo(); + vcpu.state = state; + return vcpu; + } + + @Test + public void validateCountDomainRunningVcpus() throws LibvirtException{ + VcpuInfo vcpus[] = new VcpuInfo[5]; + long valueExpected = 3; // 3 vcpus with state VIR_VCPU_RUNNING + + vcpus[0] = createVcpuInfoWithState(VcpuInfo.VcpuState.VIR_VCPU_BLOCKED); + vcpus[1] = createVcpuInfoWithState(VcpuInfo.VcpuState.VIR_VCPU_OFFLINE); + vcpus[2] = createVcpuInfoWithState(VcpuInfo.VcpuState.VIR_VCPU_RUNNING); + vcpus[3] = createVcpuInfoWithState(VcpuInfo.VcpuState.VIR_VCPU_RUNNING); + vcpus[4] = createVcpuInfoWithState(VcpuInfo.VcpuState.VIR_VCPU_RUNNING); + + Mockito.doReturn(vcpus).when(domainMock).getVcpusInfo(); + long result = LibvirtComputingResource.countDomainRunningVcpus(domainMock); + + Assert.assertEquals(valueExpected, result); + } + public void setDiskIoDriverTestIoUring() { DiskDef diskDef = configureAndTestSetDiskIoDriverTest(HYPERVISOR_LIBVIRT_VERSION_SUPPORTS_IOURING, HYPERVISOR_QEMU_VERSION_SUPPORTS_IOURING); Assert.assertEquals(DiskDef.IoDriver.IOURING, diskDef.getIoDriver()); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVmMemoryDeviceDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVmMemoryDeviceDefTest.java new file mode 100644 index 000000000000..aee4f36bda2b --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVmMemoryDeviceDefTest.java @@ -0,0 +1,41 @@ +/* + * Licensed 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.hypervisor.kvm.resource; + +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtVmMemoryDeviceDefTest { + + @Test + public void validateToString(){ + long memorySize = ByteScaleUtils.KiB; + + StringBuilder expectedToString = new StringBuilder(); + expectedToString.append(""); + expectedToString.append(""); + expectedToString.append(String.format("%s", memorySize)); + expectedToString.append("0"); + expectedToString.append(""); + expectedToString.append(""); + + Assert.assertEquals(expectedToString.toString(), new LibvirtVmMemoryDeviceDef(memorySize).toString()); + } + +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java new file mode 100644 index 000000000000..a0851e747f12 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapperTest.java @@ -0,0 +1,244 @@ +/* + * Licensed 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.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ScaleVmCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; +import junit.framework.TestCase; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.LibvirtException; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(LibvirtComputingResource.class) +public class LibvirtScaleVmCommandWrapperTest extends TestCase { + + @Spy + LibvirtScaleVmCommandWrapper libvirtScaleVmCommandWrapperSpy = Mockito.spy(LibvirtScaleVmCommandWrapper.class); + + @Mock + LibvirtComputingResource libvirtComputingResourceMock; + + @Mock + ScaleVmCommand scaleVmCommandMock; + + @Mock + LibvirtUtilitiesHelper libvirtUtilitiesHelperMock; + + @Mock + Domain domainMock; + + @Mock + Connect connectMock; + + @Mock + LibvirtException libvirtException; + + @Mock + Exception exception; + + LibvirtRequestWrapper wrapper; + VirtualMachineTO vmTo; + + String scalingDetails; + + @Before + public void init() { + wrapper = LibvirtRequestWrapper.getInstance(); + assertNotNull(wrapper); + + vmTo = new VirtualMachineTO(1, "Test 1", VirtualMachine.Type.User, 2, 1000, 67108864, 67108864, VirtualMachineTemplate.BootloaderType.External, "Other Linux (64x)", true, true, "test123"); + + long memory = ByteScaleUtils.bytesToKib(vmTo.getMaxRam()); + int vcpus = vmTo.getCpus(); + scalingDetails = String.format("%s memory to [%s KiB] and CPU cores to [%s]", vmTo.toString(), memory, vcpus); + + PowerMockito.mockStatic(LibvirtComputingResource.class); + } + + @Test + public void validateScaleVcpusRunningVcpusLessThanNewVcpusSetNewVcpu() throws LibvirtException{ + long runningVcpus = 1; + int newVcpus = 2; + + PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus); + Mockito.doNothing().when(domainMock).setVcpus(Mockito.anyInt()); + + libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails); + + Mockito.verify(domainMock).setVcpus(Mockito.anyInt()); + } + + @Test + public void validateScaleVcpusRunningVcpusEqualThanNewVcpusDoNothing() throws LibvirtException{ + long runningVcpus = 2; + int newVcpus = 2; + + PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus); + + libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails); + + Mockito.verify(domainMock, Mockito.never()).setVcpus(Mockito.anyInt()); + } + + @Test + public void validateScaleVcpusRunningVcpusHigherThanNewVcpusDoNothing() throws LibvirtException{ + long runningVcpus = 2; + int newVcpus = 1; + + PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus); + + libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails); + + Mockito.verify(domainMock, Mockito.never()).setVcpus(Mockito.anyInt()); + } + + @Test (expected = LibvirtException.class) + public void validateScaleVcpusSetVcpusThrowLibvirtException() throws LibvirtException{ + long runningVcpus = 1; + int newVcpus = 2; + + PowerMockito.when(LibvirtComputingResource.countDomainRunningVcpus(Mockito.any())).thenReturn(runningVcpus); + Mockito.doThrow(LibvirtException.class).when(domainMock).setVcpus(Mockito.anyInt()); + + libvirtScaleVmCommandWrapperSpy.scaleVcpus(domainMock, newVcpus, scalingDetails); + + Mockito.verify(domainMock, Mockito.never()).setVcpus(Mockito.anyInt()); + } + + @Test + public void validateScaleMemoryMemoryLessThanZeroDoNothing() throws LibvirtException { + long currentMemory = 1l; + long newMemory = 0l; + + PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory); + + libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails); + + Mockito.verify(domainMock, Mockito.never()).getXMLDesc(Mockito.anyInt()); + Mockito.verify(domainMock, Mockito.never()).attachDevice(Mockito.anyString()); + } + + @Test + public void validateScaleMemoryMemoryEqualToZeroDoNothing() throws LibvirtException { + long currentMemory = 1l; + long newMemory = 1l; + + PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory); + + libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails); + + Mockito.verify(domainMock, Mockito.never()).getXMLDesc(Mockito.anyInt()); + Mockito.verify(domainMock, Mockito.never()).attachDevice(Mockito.anyString()); + } + + @Test (expected = CloudRuntimeException.class) + public void validateScaleMemoryDomainXmlDoesNotContainsMaxMemory() throws LibvirtException { + long currentMemory = 1l; + long newMemory = 2l; + + PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory); + Mockito.doReturn("").when(domainMock).getXMLDesc(Mockito.anyInt()); + + libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails); + + Mockito.verify(domainMock).getXMLDesc(Mockito.anyInt()); + Mockito.verify(domainMock, Mockito.never()).attachDevice(Mockito.anyString()); + } + + @Test (expected = LibvirtException.class) + public void validateScaleMemoryAttachDeviceThrowsLibvirtException() throws LibvirtException { + long currentMemory = 1l; + long newMemory = 2l; + + PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory); + Mockito.doReturn("").when(domainMock).getXMLDesc(Mockito.anyInt()); + Mockito.doThrow(LibvirtException.class).when(domainMock).attachDevice(Mockito.anyString()); + + libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails); + + Mockito.verify(domainMock).getXMLDesc(Mockito.anyInt()); + Mockito.verify(domainMock).attachDevice(Mockito.anyString()); + } + + @Test + public void validateScaleMemory() throws LibvirtException { + long currentMemory = 1l; + long newMemory = 2l; + + PowerMockito.when(LibvirtComputingResource.getDomainMemory(Mockito.any())).thenReturn(currentMemory); + Mockito.doReturn("").when(domainMock).getXMLDesc(Mockito.anyInt()); + Mockito.doNothing().when(domainMock).attachDevice(Mockito.anyString()); + + libvirtScaleVmCommandWrapperSpy.scaleMemory(domainMock, newMemory, scalingDetails); + + Mockito.verify(domainMock).getXMLDesc(Mockito.anyInt()); + Mockito.verify(domainMock).attachDevice(Mockito.anyString()); + } + + @Test + public void validateExecuteHandleLibvirtException() throws LibvirtException { + String errorMessage = ""; + + Mockito.doReturn(vmTo).when(scaleVmCommandMock).getVirtualMachine(); + Mockito.doReturn(libvirtUtilitiesHelperMock).when(libvirtComputingResourceMock).getLibvirtUtilitiesHelper(); + Mockito.doThrow(libvirtException).when(libvirtUtilitiesHelperMock).getConnectionByVmName(Mockito.anyString()); + Mockito.doReturn(errorMessage).when(libvirtException).getMessage(); + + Answer answer = libvirtScaleVmCommandWrapperSpy.execute(scaleVmCommandMock, libvirtComputingResourceMock); + + String details = String.format("Unable to scale %s due to [%s].", scalingDetails, errorMessage); + assertFalse(answer.getResult()); + assertEquals(details, answer.getDetails()); + } + + @Test + public void validateExecuteSuccessfully() throws LibvirtException { + Mockito.doReturn(vmTo).when(scaleVmCommandMock).getVirtualMachine(); + Mockito.doReturn(libvirtUtilitiesHelperMock).when(libvirtComputingResourceMock).getLibvirtUtilitiesHelper(); + Mockito.doReturn(connectMock).when(libvirtUtilitiesHelperMock).getConnectionByVmName(Mockito.anyString()); + Mockito.doReturn(domainMock).when(connectMock).domainLookupByName(Mockito.anyString()); + Mockito.doNothing().when(libvirtScaleVmCommandWrapperSpy).scaleMemory(Mockito.any(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doNothing().when(libvirtScaleVmCommandWrapperSpy).scaleVcpus(Mockito.any(), Mockito.anyInt(), Mockito.anyString()); + + Answer answer = libvirtScaleVmCommandWrapperSpy.execute(scaleVmCommandMock, libvirtComputingResourceMock); + + String details = String.format("Successfully scaled %s.", scalingDetails); + assertTrue(answer.getResult()); + assertEquals(details, answer.getDetails()); + } + + @Test(expected = Exception.class) + public void validateExecuteThrowAnyOtherException() { + Mockito.doThrow(Exception.class).when(libvirtComputingResourceMock).getLibvirtUtilitiesHelper(); + + libvirtScaleVmCommandWrapperSpy.execute(scaleVmCommandMock, libvirtComputingResourceMock); + } +} diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index c0b1e2dc1c1c..be47685be869 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -246,7 +246,6 @@ import com.cloud.utils.net.NetUtils; import com.cloud.vm.NicIpAlias; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicIpAliasDao; import com.cloud.vm.dao.NicIpAliasVO; @@ -431,6 +430,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati "Indicates whether the host in down state can be put into maintenance state so thats its not enabled after it comes back.", true, ConfigKey.Scope.Zone, null); + public static ConfigKey VM_SERVICE_OFFERING_MAX_CPU_CORES = new ConfigKey("Advanced", Integer.class, "vm.serviceoffering.cpu.cores.max", "0", "Maximum CPU cores " + + "for vm service offering. If 0 - no limitation", true); + + public static ConfigKey VM_SERVICE_OFFERING_MAX_RAM_SIZE = new ConfigKey("Advanced", Integer.class, "vm.serviceoffering.ram.size.max", "0", "Maximum RAM size in " + + "MB for vm service offering. If 0 - no limitation", true); + public static final ConfigKey VM_USERDATA_MAX_LENGTH = new ConfigKey("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768", "Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true); @@ -2382,8 +2387,8 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) details.put(ApiConstants.MAX_CPU_NUMBER, maxCPU.toString()); } } else { - Integer maxCPUCores = VirtualMachineManager.VmServiceOfferingMaxCPUCores.value() == 0 ? Integer.MAX_VALUE: VirtualMachineManager.VmServiceOfferingMaxCPUCores.value(); - Integer maxRAMSize = VirtualMachineManager.VmServiceOfferingMaxRAMSize.value() == 0 ? Integer.MAX_VALUE: VirtualMachineManager.VmServiceOfferingMaxRAMSize.value(); + Integer maxCPUCores = VM_SERVICE_OFFERING_MAX_CPU_CORES.value() == 0 ? Integer.MAX_VALUE: VM_SERVICE_OFFERING_MAX_CPU_CORES.value(); + Integer maxRAMSize = VM_SERVICE_OFFERING_MAX_RAM_SIZE.value() == 0 ? Integer.MAX_VALUE: VM_SERVICE_OFFERING_MAX_RAM_SIZE.value(); if (cpuNumber != null && (cpuNumber.intValue() <= 0 || cpuNumber.longValue() > maxCPUCores)) { throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the cpu number value between 1 and " + maxCPUCores); } @@ -6529,7 +6534,7 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {SystemVMUseLocalStorage, IOPS_MAX_READ_LENGTH, IOPS_MAX_WRITE_LENGTH, - BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, - VM_USERDATA_MAX_LENGTH}; + BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, VM_SERVICE_OFFERING_MAX_CPU_CORES, + VM_SERVICE_OFFERING_MAX_RAM_SIZE, VM_USERDATA_MAX_LENGTH}; } } diff --git a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java index cf29a1a55a93..c18f39a3a6ba 100644 --- a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java +++ b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java @@ -20,10 +20,13 @@ import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DataStoreRole; import com.cloud.storage.GuestOSHypervisorVO; import com.cloud.storage.GuestOSVO; @@ -31,6 +34,7 @@ import com.cloud.storage.dao.GuestOSHypervisorDao; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmManager; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; import org.apache.cloudstack.storage.command.CopyCommand; @@ -42,6 +46,9 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Map; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.apache.commons.lang3.math.NumberUtils; public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { @Inject @@ -53,6 +60,9 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { @Inject DpdkHelper dpdkHelper; + @Inject + ServiceOfferingDao serviceOfferingDao; + public static final Logger s_logger = Logger.getLogger(KVMGuru.class); @Override @@ -112,32 +122,151 @@ public VirtualMachineTO implement(VirtualMachineProfile vm) { VirtualMachineTO to = toVirtualMachineTO(vm); setVmQuotaPercentage(to, vm); - if (dpdkHelper.isDpdkvHostUserModeSettingOnServiceOffering(vm)) { - dpdkHelper.setDpdkVhostUserMode(to, vm); + enableDpdkIfNeeded(vm, to); + + VirtualMachine virtualMachine = vm.getVirtualMachine(); + Long hostId = virtualMachine.getHostId(); + HostVO host = hostId == null ? null : _hostDao.findById(hostId); + + // Determine the VM's OS description + configureVmOsDescription(virtualMachine, to, host); + + configureVmMemoryAndCpuCores(to, host, virtualMachine, vm); + return to; + } + + protected void configureVmOsDescription(VirtualMachine virtualMachine, VirtualMachineTO virtualMachineTo, HostVO hostVo) { + GuestOSVO guestOS = _guestOsDao.findByIdIncludingRemoved(virtualMachine.getGuestOSId()); + String guestOsDisplayName = guestOS.getDisplayName(); + virtualMachineTo.setOs(guestOsDisplayName); + GuestOSHypervisorVO guestOsMapping = null; + + if (hostVo != null) { + guestOsMapping = _guestOsHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), getHypervisorType().toString(), hostVo.getHypervisorVersion()); + } + + if (guestOsMapping == null || hostVo == null) { + virtualMachineTo.setPlatformEmulator(guestOsDisplayName == null ? "Other" : guestOsDisplayName); + } else { + virtualMachineTo.setPlatformEmulator(guestOsMapping.getGuestOsName()); + } + } + + protected void enableDpdkIfNeeded(VirtualMachineProfile virtualMachineProfile, VirtualMachineTO virtualMachineTo) { + if (dpdkHelper.isDpdkvHostUserModeSettingOnServiceOffering(virtualMachineProfile)) { + dpdkHelper.setDpdkVhostUserMode(virtualMachineTo, virtualMachineProfile); } - if (to.getType() == VirtualMachine.Type.User && MapUtils.isNotEmpty(to.getExtraConfig()) && - to.getExtraConfig().containsKey(DpdkHelper.DPDK_NUMA) && to.getExtraConfig().containsKey(DpdkHelper.DPDK_HUGE_PAGES)) { - for (final NicTO nic : to.getNics()) { + if (virtualMachineTo.getType() == VirtualMachine.Type.User && MapUtils.isNotEmpty(virtualMachineTo.getExtraConfig()) && + virtualMachineTo.getExtraConfig().containsKey(DpdkHelper.DPDK_NUMA) && virtualMachineTo.getExtraConfig().containsKey(DpdkHelper.DPDK_HUGE_PAGES)) { + for (final NicTO nic : virtualMachineTo.getNics()) { nic.setDpdkEnabled(true); } } + } + + protected void configureVmMemoryAndCpuCores(VirtualMachineTO virtualMachineTo, HostVO hostVo, VirtualMachine virtualMachine, VirtualMachineProfile virtualMachineProfile) { + String vmDescription = virtualMachineTo.toString(); + + Pair max = getHostMaxMemoryAndCpuCores(hostVo, virtualMachine, vmDescription); + + Long maxHostMemory = max.first(); + Integer maxHostCpuCore = max.second(); + + Long minMemory = virtualMachineTo.getMinRam(); + Long maxMemory = minMemory; + Integer minCpuCores = virtualMachineTo.getCpus(); + Integer maxCpuCores = minCpuCores; + + ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId()); + if (isVmDynamicScalable(serviceOfferingVO, virtualMachineTo, virtualMachine)) { + serviceOfferingDao.loadDetails(serviceOfferingVO); + + maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory); + maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCore); + } + + virtualMachineTo.setRam(minMemory, maxMemory); + virtualMachineTo.setCpus(minCpuCores); + virtualMachineTo.setVcpuMaxLimit(maxCpuCores); + } + + protected boolean isVmDynamicScalable(ServiceOfferingVO serviceOfferingVO, VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) { + return serviceOfferingVO.isDynamic() && virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId()); + } + + protected Pair getHostMaxMemoryAndCpuCores(HostVO host, VirtualMachine virtualMachine, String vmDescription){ + Long maxHostMemory = Long.MAX_VALUE; + Integer maxHostCpuCore = Integer.MAX_VALUE; - // Determine the VM's OS description - GuestOSVO guestOS = _guestOsDao.findByIdIncludingRemoved(vm.getVirtualMachine().getGuestOSId()); - to.setOs(guestOS.getDisplayName()); - HostVO host = _hostDao.findById(vm.getVirtualMachine().getHostId()); - GuestOSHypervisorVO guestOsMapping = null; if (host != null) { - guestOsMapping = _guestOsHypervisorDao.findByOsIdAndHypervisor(guestOS.getId(), getHypervisorType().toString(), host.getHypervisorVersion()); + return new Pair<>(host.getTotalMemory(), host.getCpus()); } - if (guestOsMapping == null || host == null) { - to.setPlatformEmulator(guestOS.getDisplayName() == null ? "Other" : guestOS.getDisplayName()); + + Long lastHostId = virtualMachine.getLastHostId(); + s_logger.info(String.format("%s is not running; therefore, we use the last host [%s] that the VM was running on to derive the unconstrained service offering max CPU and memory.", vmDescription, lastHostId)); + + HostVO lastHost = lastHostId == null ? null : _hostDao.findById(lastHostId); + if (lastHost != null) { + maxHostMemory = lastHost.getTotalMemory(); + maxHostCpuCore = lastHost.getCpus(); + s_logger.debug(String.format("Retrieved memory and cpu max values {\"memory\": %s, \"cpu\": %s} from %s last %s.", maxHostMemory, maxHostCpuCore, vmDescription, lastHost.toString())); } else { - to.setPlatformEmulator(guestOsMapping.getGuestOsName()); + s_logger.warn(String.format("%s host [%s] and last host [%s] are null. Using 'Long.MAX_VALUE' [%s] and 'Integer.MAX_VALUE' [%s] as max memory and cpu cores.", vmDescription, virtualMachine.getHostId(), lastHostId, maxHostMemory, maxHostCpuCore)); } - return to; + return new Pair<>(maxHostMemory, maxHostCpuCore); + } + + protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory) { + String serviceOfferingDescription = serviceOfferingVO.toString(); + + Long maxMemory; + Integer customOfferingMaxMemory = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_MEMORY)); + Integer maxMemoryConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.value(); + if (customOfferingMaxMemory != null) { + s_logger.debug(String.format("Using 'Custom unconstrained' %s max memory value [%sMb] as %s memory.", serviceOfferingDescription, customOfferingMaxMemory, vmDescription)); + maxMemory = ByteScaleUtils.mibToBytes(customOfferingMaxMemory); + } else { + String maxMemoryConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.key(); + + s_logger.info(String.format("%s is a 'Custom unconstrained' service offering. Using config [%s] value [%s] as max %s memory.", + serviceOfferingDescription, maxMemoryConfigKey, maxMemoryConfig, vmDescription)); + + if (maxMemoryConfig > 0) { + maxMemory = ByteScaleUtils.mibToBytes(maxMemoryConfig); + } else { + s_logger.info(String.format("Config [%s] has value less or equal '0'. Using %s host or last host max memory [%s] as VM max memory in the hypervisor.", maxMemoryConfigKey, vmDescription, maxHostMemory)); + maxMemory = maxHostMemory; + } + } + return maxMemory; + } + + protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCore) { + String serviceOfferingDescription = serviceOfferingVO.toString(); + + Integer maxCpuCores; + Integer customOfferingMaxCpuCores = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_CPU_NUMBER)); + Integer maxCpuCoresConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.value(); + + if (customOfferingMaxCpuCores != null) { + s_logger.debug(String.format("Using 'Custom unconstrained' %s max cpu cores [%s] as %s cpu cores.", serviceOfferingDescription, customOfferingMaxCpuCores, vmDescription)); + maxCpuCores = customOfferingMaxCpuCores; + } else { + String maxCpuCoreConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.key(); + + s_logger.info(String.format("%s is a 'Custom unconstrained' service offering. Using config [%s] value [%s] as max %s cpu cores.", + serviceOfferingDescription, maxCpuCoreConfigKey, maxCpuCoresConfig, vmDescription)); + + if (maxCpuCoresConfig > 0) { + maxCpuCores = maxCpuCoresConfig; + } else { + s_logger.info(String.format("Config [%s] has value less or equal '0'. Using %s host or last host max cpu cores [%s] as VM cpu cores in the hypervisor.", maxCpuCoreConfigKey, vmDescription, maxHostCpuCore)); + maxCpuCores = maxHostCpuCore; + } + } + return maxCpuCores; } @Override diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 00c8d9829bb2..44912a08f38c 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -155,6 +155,7 @@ import com.cloud.capacity.CapacityManager; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; @@ -342,6 +343,8 @@ import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import java.util.HashSet; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; @@ -1137,11 +1140,11 @@ public UserVm upgradeVirtualMachine(UpgradeVMCmd cmd) throws ResourceAllocationE } private void validateOfferingMaxResource(ServiceOfferingVO offering) { - Integer maxCPUCores = VirtualMachineManager.VmServiceOfferingMaxCPUCores.value() == 0 ? Integer.MAX_VALUE: VirtualMachineManager.VmServiceOfferingMaxCPUCores.value(); + Integer maxCPUCores = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.value() == 0 ? Integer.MAX_VALUE: ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.value(); if (offering.getCpu() > maxCPUCores) { throw new InvalidParameterValueException("Invalid cpu cores value, please choose another service offering with cpu cores between 1 and " + maxCPUCores); } - Integer maxRAMSize = VirtualMachineManager.VmServiceOfferingMaxRAMSize.value() == 0 ? Integer.MAX_VALUE: VirtualMachineManager.VmServiceOfferingMaxRAMSize.value(); + Integer maxRAMSize = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.value() == 0 ? Integer.MAX_VALUE: ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.value(); if (offering.getRamSize() > maxRAMSize) { throw new InvalidParameterValueException("Invalid memory value, please choose another service offering with memory between 32 and " + maxRAMSize + " MB"); } @@ -1156,7 +1159,7 @@ public void validateCustomParameters(ServiceOfferingVO serviceOffering, Map maxCPU || cpuNumber > maxCPUCores) { throw new InvalidParameterValueException(String.format("Invalid cpu cores value, specify a value between %d and %d", minCPU, Math.min(maxCPUCores, maxCPU))); } @@ -1179,7 +1182,7 @@ public void validateCustomParameters(ServiceOfferingVO serviceOffering, Map maxMemory || memory > maxRAMSize) { throw new InvalidParameterValueException(String.format("Invalid memory value, specify a value between %d and %d", minMemory, Math.min(maxRAMSize, maxMemory))); } @@ -1873,9 +1876,19 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI Account caller = CallContext.current().getCallingAccount(); VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId); - if (vmInstance.getHypervisorType() != HypervisorType.XenServer && vmInstance.getHypervisorType() != HypervisorType.VMware && vmInstance.getHypervisorType() != HypervisorType.Simulator) { - s_logger.info("Scaling the VM dynamically is not supported for VMs running on Hypervisor "+vmInstance.getHypervisorType()); - throw new InvalidParameterValueException("Scaling the VM dynamically is not supported for VMs running on Hypervisor "+vmInstance.getHypervisorType()); + + Set supportedHypervisorTypes = new HashSet<>(); + supportedHypervisorTypes.add(HypervisorType.XenServer); + supportedHypervisorTypes.add(HypervisorType.VMware); + supportedHypervisorTypes.add(HypervisorType.Simulator); + supportedHypervisorTypes.add(HypervisorType.KVM); + + HypervisorType vmHypervisorType = vmInstance.getHypervisorType(); + + if (!supportedHypervisorTypes.contains(vmHypervisorType)) { + String message = String.format("Scaling the VM dynamically is not supported for VMs running on Hypervisor [%s].", vmInstance.getHypervisorType()); + s_logger.info(message); + throw new InvalidParameterValueException(message); } _accountMgr.checkAccess(caller, null, true, vmInstance); @@ -1907,9 +1920,17 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI // Don't allow to scale when (Any of the new values less than current values) OR (All current and new values are same) if ((newSpeed < currentSpeed || newMemory < currentMemory || newCpu < currentCpu) || (newSpeed == currentSpeed && newMemory == currentMemory && newCpu == currentCpu)) { - throw new InvalidParameterValueException("Only scaling up the vm is supported, new service offering(speed=" + newSpeed + ",cpu=" + newCpu + ",memory=," + newMemory - + ")" + " should have at least one value(cpu/ram) greater than old value and no resource value less than older(speed=" + currentSpeed + ",cpu=" + currentCpu - + ",memory=," + currentMemory + ")"); + String message = String.format("While the VM is running, only scalling up it is supported. New service offering {\"memory\": %s, \"speed\": %s, \"cpu\": %s} should" + + " have at least one value (ram, speed or cpu) greater than the current values {\"memory\": %s, \"speed\": %s, \"cpu\": %s}.", newMemory, newSpeed, newCpu, + currentMemory, currentSpeed, currentCpu); + + throw new InvalidParameterValueException(message); + } + + if (vmHypervisorType.equals(HypervisorType.KVM) && !currentServiceOffering.isDynamic()) { + String message = String.format("Unable to live scale VM on KVM when current service offering is a \"Fixed Offering\". KVM needs the tag \"maxMemory\" to live scale and it is only configured when VM is deployed with a custom service offering and \"Dynamic Scalable\" is enabled."); + s_logger.info(message); + throw new InvalidParameterValueException(message); } _offeringDao.loadDetails(currentServiceOffering); @@ -1919,18 +1940,18 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI Map newDetails = newServiceOffering.getDetails(); String currentVgpuType = currentDetails.get("vgpuType"); String newVgpuType = newDetails.get("vgpuType"); - if(currentVgpuType != null) { - if(newVgpuType == null || !newVgpuType.equalsIgnoreCase(currentVgpuType)) { - throw new InvalidParameterValueException("Dynamic scaling of vGPU type is not supported. VM has vGPU Type: " + currentVgpuType); - } + + if (currentVgpuType != null && (newVgpuType == null || !newVgpuType.equalsIgnoreCase(currentVgpuType))) { + throw new InvalidParameterValueException(String.format("Dynamic scaling of vGPU type is not supported. VM has vGPU Type: [%s].", currentVgpuType)); } // Check resource limits if (newCpu > currentCpu) { _resourceLimitMgr.checkResourceLimit(caller, ResourceType.cpu, newCpu - currentCpu); } + if (newMemory > currentMemory) { - _resourceLimitMgr.checkResourceLimit(caller, ResourceType.memory, newMemory - currentMemory); + _resourceLimitMgr.checkResourceLimit(caller, ResourceType.memory, memoryDiff); } // Dynamically upgrade the running vms @@ -1942,18 +1963,18 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI // Check zone wide flag boolean enableDynamicallyScaleVm = EnableDynamicallyScaleVm.valueIn(vmInstance.getDataCenterId()); if (!enableDynamicallyScaleVm) { - throw new PermissionDeniedException("Dynamically scaling virtual machines is disabled for this zone, please contact your admin"); + throw new PermissionDeniedException("Dynamically scaling virtual machines is disabled for this zone, please contact your admin."); } // Check vm flag if (!vmInstance.isDynamicallyScalable()) { - throw new CloudRuntimeException("Unable to Scale the VM: " + vmInstance.getUuid() + " as VM is not configured to be dynamically scalable"); + throw new CloudRuntimeException(String.format("Unable to scale %s as it does not have tools to support dynamic scaling.", vmInstance.toString())); } // Check disable threshold for cluster is not crossed HostVO host = _hostDao.findById(vmInstance.getHostId()); if (_capacityMgr.checkIfClusterCrossesThreshold(host.getClusterId(), cpuDiff, memoryDiff)) { - throw new CloudRuntimeException("Unable to scale vm: " + vmInstance.getUuid() + " due to insufficient resources"); + throw new CloudRuntimeException(String.format("Unable to scale %s due to insufficient resources.", vmInstance.toString())); } while (retry-- != 0) { // It's != so that it can match -1. @@ -1972,7 +1993,7 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI // #1 Check existing host has capacity if (!excludes.shouldAvoid(ApiDBUtils.findHostById(vmInstance.getHostId()))) { existingHostHasCapacity = _capacityMgr.checkIfHostHasCpuCapability(vmInstance.getHostId(), newCpu, newSpeed) - && _capacityMgr.checkIfHostHasCapacity(vmInstance.getHostId(), cpuDiff, (memoryDiff) * 1024L * 1024L, false, + && _capacityMgr.checkIfHostHasCapacity(vmInstance.getHostId(), cpuDiff, ByteScaleUtils.mibToBytes(memoryDiff), false, _capacityMgr.getClusterOverProvisioningFactor(host.getClusterId(), Capacity.CAPACITY_TYPE_CPU), _capacityMgr.getClusterOverProvisioningFactor(host.getClusterId(), Capacity.CAPACITY_TYPE_MEMORY), false); excludes.addHost(vmInstance.getHostId()); @@ -1989,9 +2010,7 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI success = true; return success; } catch (InsufficientCapacityException | ResourceUnavailableException | ConcurrentOperationException e) { - s_logger.warn("Received exception while scaling ", e); - } catch (Exception e) { - s_logger.warn("Scaling failed with exception: ", e); + s_logger.error(String.format("Unable to scale %s due to [%s].", vmInstance.toString(), e.getMessage()), e); } finally { if (!success) { // Decrement CPU and Memory count accordingly. diff --git a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java index a10e9379fd85..22f54d0b13e2 100644 --- a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java +++ b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java @@ -16,12 +16,22 @@ // under the License. package com.cloud.hypervisor; +import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; import com.cloud.offering.ServiceOffering; import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.GuestOSHypervisorVO; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.GuestOSHypervisorDao; +import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; @@ -37,6 +47,11 @@ import java.io.UnsupportedEncodingException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; +import org.junit.Assert; @RunWith(MockitoJUnitRunner.class) public class KVMGuruTest { @@ -65,6 +80,30 @@ public class KVMGuruTest { @Mock ServiceOfferingDetailsVO detail2; + @Mock + ServiceOfferingVO serviceOfferingVoMock; + + @Mock + VirtualMachine virtualMachineMock; + + @Mock + ServiceOfferingDao serviceOfferingDaoMock; + + @Mock + DpdkHelper dpdkHelperMock; + + @Mock + GuestOSVO guestOsVoMock; + + @Mock + GuestOSHypervisorVO guestOsMappingMock; + + @Mock + GuestOSHypervisorDao guestOSHypervisorDaoMock; + + @Mock + GuestOSDao guestOsDaoMock; + private static final long hostId = 1L; private static final Long offeringId = 1L; @@ -129,4 +168,288 @@ public void testSetVmQuotaPercentageOverProvision() { guru.setVmQuotaPercentage(vmTO, vmProfile); Mockito.verify(vmTO).setCpuQuotaPercentage(1d); } + + @Test + public void validateGetVmMaxMemoryReturnCustomOfferingMaxMemory(){ + int maxCustomOfferingMemory = 64; + Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(String.valueOf(maxCustomOfferingMemory)); + + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1l); + + Assert.assertEquals(ByteScaleUtils.mibToBytes(maxCustomOfferingMemory), result); + } + + @Test + public void validateGetVmMaxMemoryReturnVmServiceOfferingMaxRAMSize(){ + int maxMemoryConfig = 64; + Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(null); + + ConfigKey vmServiceOfferingMaxRAMSize = Mockito.mock(ConfigKey.class); + ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE = vmServiceOfferingMaxRAMSize; + + Mockito.when(vmServiceOfferingMaxRAMSize.value()).thenReturn(maxMemoryConfig); + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1l); + + Assert.assertEquals(ByteScaleUtils.mibToBytes(maxMemoryConfig), result); + } + + @Test + public void validateGetVmMaxMemoryReturnMaxHostMemory(){ + long maxHostMemory = ByteScaleUtils.mibToBytes(2000); + Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(null); + + ConfigKey vmServiceOfferingMaxRAMSize = Mockito.mock(ConfigKey.class); + ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE = vmServiceOfferingMaxRAMSize; + + Mockito.when(vmServiceOfferingMaxRAMSize.value()).thenReturn(0); + + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", maxHostMemory); + + Assert.assertEquals(maxHostMemory, result); + } + + @Test + public void validateGetVmMaxCpuCoresReturnCustomOfferingMaxCpuCores(){ + int maxCustomOfferingCpuCores = 16; + Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(String.valueOf(maxCustomOfferingCpuCores)); + + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", 1); + + Assert.assertEquals(maxCustomOfferingCpuCores, result); + } + + @Test + public void validateGetVmMaxCpuCoresVmServiceOfferingMaxCPUCores(){ + int maxCpuCoresConfig = 16; + Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(null); + + ConfigKey vmServiceOfferingMaxCPUCores = Mockito.mock(ConfigKey.class); + ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES = vmServiceOfferingMaxCPUCores; + + Mockito.when(vmServiceOfferingMaxCPUCores.value()).thenReturn(maxCpuCoresConfig); + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", 1); + + Assert.assertEquals(maxCpuCoresConfig, result); + } + + @Test + public void validateGetVmMaxCpuCoresReturnMaxHostMemory(){ + int maxHostCpuCores = 64; + Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(null); + + ConfigKey vmServiceOfferingMaxCPUCores = Mockito.mock(ConfigKey.class); + ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES = vmServiceOfferingMaxCPUCores; + + Mockito.when(vmServiceOfferingMaxCPUCores.value()).thenReturn(0); + + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", maxHostCpuCores); + + Assert.assertEquals(maxHostCpuCores, result); + } + + @Test + public void validateGetHostMaxMemoryAndCpuCoresHostNotNull(){ + Long maxMemory = 2048l; + Integer maxCpuCores = 16; + + Mockito.when(host.getTotalMemory()).thenReturn(maxMemory); + Mockito.when(host.getCpus()).thenReturn(maxCpuCores); + + Pair result = guru.getHostMaxMemoryAndCpuCores(host, virtualMachineMock, "Vm description"); + + Assert.assertEquals(new Pair<>(maxMemory, maxCpuCores), result); + } + + @Test + public void validateGetHostMaxMemoryAndCpuCoresHostNullAndLastHostIdNull(){ + Long maxMemory = Long.MAX_VALUE; + Integer maxCpuCores = Integer.MAX_VALUE; + + Pair result = guru.getHostMaxMemoryAndCpuCores(null, virtualMachineMock, "Vm description"); + + Assert.assertEquals(new Pair<>(maxMemory, maxCpuCores), result); + } + + @Test + public void validateGetHostMaxMemoryAndCpuCoresHostNullAndLastHostIdNotNullAndLastHostNull(){ + Long maxMemory = Long.MAX_VALUE; + Integer maxCpuCores = Integer.MAX_VALUE; + guru._hostDao = hostDao; + + Mockito.when(virtualMachineMock.getLastHostId()).thenReturn(1l); + Mockito.doReturn(null).when(hostDao).findById(Mockito.any()); + + Pair result = guru.getHostMaxMemoryAndCpuCores(null, virtualMachineMock, "Vm description"); + + Assert.assertEquals(new Pair<>(maxMemory, maxCpuCores), result); + } + + @Test + public void validateGetHostMaxMemoryAndCpuCoresHostNullAndLastHostIdNotNullAndLastHostNotNull(){ + Long maxMemory = 2048l; + Integer maxCpuCores = 16; + guru._hostDao = hostDao; + + Mockito.when(virtualMachineMock.getLastHostId()).thenReturn(1l); + Mockito.doReturn(host).when(hostDao).findById(Mockito.any()); + Mockito.when(host.getTotalMemory()).thenReturn(maxMemory); + Mockito.when(host.getCpus()).thenReturn(maxCpuCores); + + Pair result = guru.getHostMaxMemoryAndCpuCores(null, virtualMachineMock, "Vm description"); + + Assert.assertEquals(new Pair<>(maxMemory, maxCpuCores), result); + } + + @Test + public void validateConfigureVmMemoryAndCpuCoresServiceOfferingIsDynamicAndVmIsDynamicCallGetMethods(){ + guru.serviceOfferingDao = serviceOfferingDaoMock; + + Mockito.doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(true).when(guru).isVmDynamicScalable(Mockito.any(), Mockito.any(), Mockito.any()); + + guru.configureVmMemoryAndCpuCores(vmTO, host, virtualMachineMock, vmProfile); + + Mockito.verify(guru).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong()); + Mockito.verify(guru).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt()); + } + + @Test + public void validateConfigureVmMemoryAndCpuCoresServiceOfferingIsNotDynamicAndVmIsDynamicDoNotCallGetMethods(){ + guru.serviceOfferingDao = serviceOfferingDaoMock; + + Mockito.doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(false).when(guru).isVmDynamicScalable(Mockito.any(), Mockito.any(), Mockito.any()); + + guru.configureVmMemoryAndCpuCores(vmTO, host, virtualMachineMock, vmProfile); + + Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong()); + Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt()); + } + + @Test + public void validateConfigureVmMemoryAndCpuCoresServiceOfferingIsDynamicAndVmIsNotDynamicDoNotCallGetMethods(){ + guru.serviceOfferingDao = serviceOfferingDaoMock; + + Mockito.doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(true).when(serviceOfferingVoMock).isDynamic(); + Mockito.doReturn(false).when(vmTO).isEnableDynamicallyScaleVm(); + + guru.configureVmMemoryAndCpuCores(vmTO, host, virtualMachineMock, vmProfile); + + Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong()); + Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt()); + } + + @Test + public void validateEnableDpdkIfNeededCallDpdkHelperSetDpdkVhostUserMode() { + Mockito.when(dpdkHelperMock.isDpdkvHostUserModeSettingOnServiceOffering(vmProfile)).thenReturn(Boolean.TRUE); + guru.enableDpdkIfNeeded(vmProfile, vmTO); + Mockito.verify(dpdkHelperMock).setDpdkVhostUserMode(vmTO, vmProfile); + } + + @Test + public void validateEnableDpdkIfNeededDoNotCallDpdkHelperSetDpdkVhostUserMode() { + Mockito.when(dpdkHelperMock.isDpdkvHostUserModeSettingOnServiceOffering(vmProfile)).thenReturn(Boolean.FALSE); + guru.enableDpdkIfNeeded(vmProfile, vmTO); + Mockito.verify(dpdkHelperMock, Mockito.times(0)).setDpdkVhostUserMode(vmTO, vmProfile); + } + + @Test + public void validateEnableDpdkIfNeededNicSetDpdkEnabledTrue() { + Map map = new HashMap<>(); + map.put(DpdkHelper.DPDK_NUMA, "test1"); + map.put(DpdkHelper.DPDK_HUGE_PAGES, "test2"); + + NicTO nicTo1 = Mockito.mock(NicTO.class); + NicTO nicTo2 = Mockito.mock(NicTO.class); + NicTO nicTo3 = Mockito.mock(NicTO.class); + + NicTO[] nics = {nicTo1, nicTo2, nicTo3}; + + Mockito.when(vmTO.getType()).thenReturn(VirtualMachine.Type.User); + Mockito.when(vmTO.getExtraConfig()).thenReturn(map); + Mockito.when(vmTO.getNics()).thenReturn(nics); + + guru.enableDpdkIfNeeded(vmProfile, vmTO); + + for (NicTO nic : nics) { + Mockito.verify(nic).setDpdkEnabled(true); + } + } + + @Test + public void validateConfigureVmOsDescriptionHostNotNullAndGuestOsMappingNotNullAndGuestOsDisplayNameNotNull(){ + guru._guestOsDao = guestOsDaoMock; + guru._guestOsHypervisorDao = guestOSHypervisorDaoMock; + + VirtualMachineTO virtualMachineTo = new VirtualMachineTO() {}; + String platformEmulator = "Ubuntu"; + + Mockito.doReturn(guestOsVoMock).when(guestOsDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(guestOsMappingMock).when(guestOSHypervisorDaoMock).findByOsIdAndHypervisor(Mockito.anyLong(), Mockito.anyString(), Mockito.any()); + Mockito.doReturn(platformEmulator).when(guestOsMappingMock).getGuestOsName(); + + guru.configureVmOsDescription(virtualMachineMock, virtualMachineTo, host); + + Assert.assertEquals(platformEmulator, virtualMachineTo.getPlatformEmulator()); + } + + @Test + public void validateConfigureVmOsDescriptionHostNotNullAndGuestOsMappingNullAndGuestOsDisplayNameNull(){ + guru._guestOsDao = guestOsDaoMock; + guru._guestOsHypervisorDao = guestOSHypervisorDaoMock; + + VirtualMachineTO virtualMachineTo = new VirtualMachineTO() {}; + + Mockito.doReturn(guestOsVoMock).when(guestOsDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(null).when(guestOSHypervisorDaoMock).findByOsIdAndHypervisor(Mockito.anyLong(), Mockito.anyString(), Mockito.any()); + + guru.configureVmOsDescription(virtualMachineMock, virtualMachineTo, host); + + Assert.assertEquals("Other", virtualMachineTo.getPlatformEmulator()); + } + + @Test + public void validateConfigureVmOsDescriptionHostNotNullAndGuestOsMappingNullAndGuestOsDisplayNameNotNull(){ + guru._guestOsDao = guestOsDaoMock; + guru._guestOsHypervisorDao = guestOSHypervisorDaoMock; + + VirtualMachineTO virtualMachineTo = new VirtualMachineTO() {}; + String platformEmulator = "Ubuntu"; + + Mockito.doReturn(guestOsVoMock).when(guestOsDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(null).when(guestOSHypervisorDaoMock).findByOsIdAndHypervisor(Mockito.anyLong(), Mockito.anyString(), Mockito.any()); + Mockito.doReturn(platformEmulator).when(guestOsVoMock).getDisplayName(); + + guru.configureVmOsDescription(virtualMachineMock, virtualMachineTo, host); + + Assert.assertEquals(platformEmulator, virtualMachineTo.getPlatformEmulator()); + } + + @Test + public void validateConfigureVmOsDescriptionHostNullAndGuestOsMappingNullAndGuestOsDisplayNameNull(){ + guru._guestOsDao = guestOsDaoMock; + VirtualMachineTO virtualMachineTo = new VirtualMachineTO() {}; + + Mockito.doReturn(guestOsVoMock).when(guestOsDaoMock).findByIdIncludingRemoved(Mockito.any()); + guru.configureVmOsDescription(virtualMachineMock, virtualMachineTo, host); + + Assert.assertEquals("Other", virtualMachineTo.getPlatformEmulator()); + } + + @Test + public void validateConfigureVmOsDescriptionHostNullAndGuestOsMappingNullAndGuestOsDisplayNameNotNull(){ + guru._guestOsDao = guestOsDaoMock; + + VirtualMachineTO virtualMachineTo = new VirtualMachineTO() {}; + String platformEmulator = "Ubuntu"; + + Mockito.doReturn(guestOsVoMock).when(guestOsDaoMock).findByIdIncludingRemoved(Mockito.any()); + Mockito.doReturn(platformEmulator).when(guestOsVoMock).getDisplayName(); + + guru.configureVmOsDescription(virtualMachineMock, virtualMachineTo, host); + + Assert.assertEquals(platformEmulator, virtualMachineTo.getPlatformEmulator()); + } + } \ No newline at end of file diff --git a/utils/src/main/java/org/apache/cloudstack/utils/bytescale/ByteScaleUtils.java b/utils/src/main/java/org/apache/cloudstack/utils/bytescale/ByteScaleUtils.java new file mode 100644 index 000000000000..95d455f12474 --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/bytescale/ByteScaleUtils.java @@ -0,0 +1,47 @@ +/* + * Licensed 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.utils.bytescale; + +/** + * This class provides a facility to convert bytes through his scales (b, Kib, Kb, Mib, Mb...). + * + */ +public class ByteScaleUtils { + + public static final long KiB = 1024; + public static final long MiB = KiB * 1024; + + private ByteScaleUtils() {} + + /** + * Converts mebibytes to bytes. + * + * @param mib The value to convert to bytes (eq: 1, 2, 3, ..., 42,...). + * @return The parameter multiplied by 1048576 (1024 * 1024, 1 MiB). + */ + public static long mibToBytes(long mib) { + return mib * MiB; + } + + /** + * Converts bytes to kibibytes. + * + * @param b The value in bytes to convert to kibibytes. + * @return The parameter divided by 1024 (1 KiB). + */ + public static long bytesToKib(long b) { + return b / KiB; + } +} diff --git a/utils/src/test/java/org/apache/cloudstack/utils/bytescale/ByteScaleUtilsTest.java b/utils/src/test/java/org/apache/cloudstack/utils/bytescale/ByteScaleUtilsTest.java new file mode 100644 index 000000000000..b3487641b42c --- /dev/null +++ b/utils/src/test/java/org/apache/cloudstack/utils/bytescale/ByteScaleUtilsTest.java @@ -0,0 +1,51 @@ +/* + * Licensed 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.utils.bytescale; + +import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ByteScaleUtilsTest extends TestCase { + + @Test + public void validateMibToBytes() { + long mib = 3000L; + long b = 1024L * 1024L * mib; + assertEquals(b, ByteScaleUtils.mibToBytes(mib)); + } + + @Test + public void validateBytesToKib() { + long kib = 1024L * 3000L; + long b = 1024 * kib; + assertEquals(kib, ByteScaleUtils.bytesToKib(b)); + } + + @Test + public void validateMibToBytesIfIntTimesIntThenMustExtrapolateIntMaxValue() { + int mib = 3000; + long b = 1024L * 1024L * mib; + assertEquals(b, ByteScaleUtils.mibToBytes(mib)); + } + + @Test + public void validateBytesToKibIfIntByIntThenMustExtrapolateIntMaxValue(){ + int b = Integer.MAX_VALUE; + assertEquals(b, ByteScaleUtils.bytesToKib(b * 1024L)); + } +}