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 8a30b5ef9fe..5fc248343ec 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 class VirtualMachineTO { 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 1c763dd05dd..dc63b1ee746 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 class ScaleVmCommand extends Command { 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 f8596998b18..21457500808 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 @@ package com.cloud.resource; 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 e7828fabef8..68183ad2add 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 eca0852060d..1c77295a75a 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 class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac _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 class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac 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 193131850b9..776e48c2730 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 class HostVO implements Host { @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 44f39a1050b..a2362316420 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 class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering } @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 df729cbe355..23943075773 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; 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 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv 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 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv 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 class LibvirtComputingResource extends ServerResourceBase implements Serv } } + /** + * 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 c762cbff48a..78d17444523 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 class LibvirtVMDef { } 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; - } - - public void setMemBacking(String memBacking) { - _memBacking = memBacking; + this.currentMemory = currMem; } public void setVcpuNum(int vcpu) { - _vcpu = vcpu; + this.vcpu = vcpu; } - public void setMemBalloning(boolean turnon) { - _memBalloning = turnon; + public void setMaxVcpuNum(int maxVcpu) { + this.maxVcpu = maxVcpu; + } + + public int getVcpu() { + return vcpu; + } + + public int getMaxVcpu() { + return maxVcpu; + } + + 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"); + 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)); } - 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"); - } - 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 00000000000..1865a6d41d5 --- /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 00000000000..384d5cc8b15 --- /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 86f30bf3bc3..fbf1ec614ad 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.DiskProfile; 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 class LibvirtComputingResourceTest { 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 @@ public class LibvirtComputingResourceTest { } 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 class LibvirtComputingResourceTest { 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 00000000000..aee4f36bda2 --- /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 00000000000..a0851e747f1 --- /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 13761b50e0e..afabf1bfd9e 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -247,7 +247,6 @@ import com.cloud.utils.exception.CloudRuntimeException; 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; @@ -432,6 +431,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); @@ -2383,8 +2388,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati 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 class ConfigurationManagerImpl extends ManagerBase implements Configurati @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 cf29a1a55a9..c18f39a3a6b 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.Command; 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.GuestOSDao; 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 javax.inject.Inject; 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 class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { 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 (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 (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 (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()); - } - if (guestOsMapping == null || host == null) { - to.setPlatformEmulator(guestOS.getDisplayName() == null ? "Other" : guestOS.getDisplayName()); - } else { - to.setPlatformEmulator(guestOsMapping.getGuestOsName()); + return new Pair<>(host.getTotalMemory(), host.getCpus()); } - return to; + 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 { + 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 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 00c8d9829bb..44912a08f38 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.Capacity; 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.dao.VMInstanceDao; 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir int minCPU = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MIN_CPU_NUMBER), 1); int maxCPU = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MAX_CPU_NUMBER), Integer.MAX_VALUE); int cpuNumber = NumbersUtil.parseInt(customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()), -1); - 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 (cpuNumber < minCPU || cpuNumber > 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 class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir int minMemory = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MIN_MEMORY), 32); int maxMemory = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MAX_MEMORY), Integer.MAX_VALUE); int memory = NumbersUtil.parseInt(customParameters.get(UsageEventVO.DynamicParameters.memory.name()), -1); - 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 (memory < minMemory || memory > 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // 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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // #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 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir 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 a10e9379fd8..22f54d0b13e 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 org.mockito.runners.MockitoJUnitRunner; 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 class KVMGuruTest { 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 00000000000..95d455f1247 --- /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 00000000000..b3487641b42 --- /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)); + } +}