From 6b831f5196c3cfcc2d7aaf3fc2150c069d125fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Fri, 22 May 2026 08:19:21 -0300 Subject: [PATCH] Live scaling for VMs with fixed service offerings on KVM (#12975) --- .../cloud/agent/api/to/VirtualMachineTO.java | 8 +- .../com/cloud/agent/api/ScaleVmCommand.java | 9 +- .../com/cloud/capacity/CapacityManager.java | 14 ++ .../cloud/vm/VirtualMachineManagerImpl.java | 40 +++-- .../META-INF/db/schema-42210to42300.sql | 18 ++ .../resource/LibvirtComputingResource.java | 159 ++++++++++++++---- .../kvm/resource/LibvirtDomainXMLParser.java | 2 +- .../hypervisor/kvm/resource/LibvirtVMDef.java | 17 +- .../wrapper/LibvirtScaleVmCommandWrapper.java | 5 +- .../LibvirtComputingResourceTest.java | 116 +++++++++++-- .../cloud/capacity/CapacityManagerImpl.java | 2 +- .../java/com/cloud/hypervisor/KVMGuru.java | 150 ++++++++--------- .../main/java/com/cloud/vm/UserVmManager.java | 4 + .../java/com/cloud/vm/UserVmManagerImpl.java | 36 ++-- .../com/cloud/hypervisor/KVMGuruTest.java | 123 +++++++------- .../component/test_escalations_instances.py | 3 - .../component/test_escalations_ipaddresses.py | 9 +- ui/public/locales/el_GR.json | 2 - ui/public/locales/en.json | 1 - ui/public/locales/ja_JP.json | 1 - ui/public/locales/pt_BR.json | 1 - ui/public/locales/te.json | 1 - ui/public/locales/zh_CN.json | 1 - ui/src/views/compute/ScaleVM.vue | 37 ++-- .../views/compute/wizard/ComputeSelection.vue | 12 +- 25 files changed, 504 insertions(+), 267 deletions(-) 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 e26cc1e9f02..9af6c731fd2 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 @@ -51,6 +51,7 @@ public class VirtualMachineTO { private long minRam; private long maxRam; + private long requestedRam; private String hostName; private String arch; private String os; @@ -207,15 +208,20 @@ public class VirtualMachineTO { return minRam; } - public void setRam(long minRam, long maxRam) { + public void setRam(long minRam, long maxRam, long requestedRam) { this.minRam = minRam; this.maxRam = maxRam; + this.requestedRam = requestedRam; } public long getMaxRam() { return maxRam; } + public long getRequestedRam() { + return requestedRam; + } + public String getHostName() { return hostName; } 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 dc63b1ee746..e2953e9c7ad 100644 --- a/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java @@ -30,6 +30,7 @@ public class ScaleVmCommand extends Command { Integer maxSpeed; long minRam; long maxRam; + private boolean limitCpuUseChange; public VirtualMachineTO getVm() { return vm; @@ -43,7 +44,7 @@ public class ScaleVmCommand extends Command { return cpus; } - public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpeed, long minRam, long maxRam, boolean limitCpuUse) { + public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpeed, long minRam, long maxRam, boolean limitCpuUse, Double cpuQuotaPercentage, boolean limitCpuUseChange) { super(); this.vmName = vmName; this.cpus = cpus; @@ -52,6 +53,8 @@ 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); + this.vm.setCpuQuotaPercentage(cpuQuotaPercentage); + this.limitCpuUseChange = limitCpuUseChange; } public void setCpus(int cpus) { @@ -102,6 +105,10 @@ public class ScaleVmCommand extends Command { return vm; } + public boolean getLimitCpuUseChange() { + return limitCpuUseChange; + } + @Override public boolean executeInSequence() { return true; diff --git a/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java b/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java index 4c81c7359f2..abaf6ea967d 100644 --- a/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java +++ b/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java @@ -133,6 +133,20 @@ public interface CapacityManager { "capacity.calculate.workers", "1", "Number of worker threads to be used for capacities calculation", true); + ConfigKey KvmMemoryDynamicScalingCapacity = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, + Integer.class, "kvm.memory.dynamic.scaling.capacity", "0", + "Defines the maximum memory capacity in MiB for which VMs can be dynamically scaled to with KVM. " + + "The 'kvm.memory.dynamic.scaling.capacity' setting's value will be used to define the value of the " + + "'' element of domain XMLs. If it is set to a value less than or equal to '0', then the host's memory capacity will be considered.", + true, ConfigKey.Scope.Cluster); + + ConfigKey KvmCpuDynamicScalingCapacity = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, + Integer.class, "kvm.cpu.dynamic.scaling.capacity", "0", + "Defines the maximum vCPU capacity for which VMs can be dynamically scaled to with KVM. " + + "The 'kvm.cpu.dynamic.scaling.capacity' setting's value will be used to define the value of the " + + "'' element of domain XMLs. If it is set to a value less than or equal to '0', then the host's CPU cores capacity will be considered.", + true, ConfigKey.Scope.Cluster); + public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId); void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost); 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 17ddf870670..ead990b42b8 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -50,6 +50,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; +import com.cloud.hypervisor.KVMGuru; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -5162,7 +5163,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac try { result = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); } catch (Exception ex) { - throw new RuntimeException("Unhandled exception", ex); + throw new RuntimeException("Unable to reconfigure VM.", ex); } if (result != null) { @@ -5175,22 +5176,29 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); + VMInstanceVO vm = _vmDao.findByUuid(vmUuid); HostVO hostVo = _hostDao.findById(vm.getHostId()); - Long clustedId = hostVo.getClusterId(); - Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clustedId); - Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clustedId); - boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clustedId); - boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clustedId); + Long clusterId = hostVo.getClusterId(); + Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clusterId); + Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clusterId); + boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clusterId); + boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clusterId); int minMemory = (int)(newServiceOffering.getRamSize() / (divideMemoryByOverprovisioning ? memoryOvercommitRatio : 1)); int minSpeed = (int)(newServiceOffering.getSpeed() / (divideCpuByOverprovisioning ? cpuOvercommitRatio : 1)); - ScaleVmCommand scaleVmCommand = - new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, - newServiceOffering.getSpeed(), minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, newServiceOffering.getLimitCpuUse()); + Double cpuQuotaPercentage = null; + if (newServiceOffering.getLimitCpuUse() && vm.getHypervisorType().equals(HypervisorType.KVM)) { + KVMGuru kvmGuru = (KVMGuru) _hvGuruMgr.getGuru(vm.getHypervisorType()); + cpuQuotaPercentage = kvmGuru.getCpuQuotaPercentage(minSpeed, hostVo.getSpeed()); + } + + boolean limitCpuUseChange = oldServiceOffering.getLimitCpuUse() != newServiceOffering.getLimitCpuUse(); + ScaleVmCommand scaleVmCommand = new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, newServiceOffering.getSpeed(), + minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, + newServiceOffering.getLimitCpuUse(), cpuQuotaPercentage, limitCpuUseChange); scaleVmCommand.getVirtualMachine().setId(vm.getId()); scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid()); @@ -5219,16 +5227,20 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); } - upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + if (reconfiguringOnExistingHost) { + _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); + } + + boolean vmUpgraded = upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + if (vmUpgraded) { + vm = _vmDao.findById(vm.getId()); + } if (vm.getType().equals(VirtualMachine.Type.User)) { _userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE); } if (reconfiguringOnExistingHost) { - vm.setServiceOfferingId(oldServiceOffering.getId()); - _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); - vm.setServiceOfferingId(newServiceOffering.getId()); _capacityMgr.allocateVmCapacity(vm, false); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index c99f798d3d5..dfa5fa8f3a1 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -131,3 +131,21 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` ( -- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); + +-- Creates the 'kvm.memory.dynamic.scaling.capacity' and, for already active ACS environments, +-- initializes it with the value of the setting 'vm.serviceoffering.ram.size.max' +INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `default_value`, `updated`, `scope`, `is_dynamic`, `group_id`, `subgroup_id`, `display_text`, `description`) +SELECT 'Advanced', 'DEFAULT', 'CapacityManager', 'kvm.memory.dynamic.scaling.capacity', `cfg`.`value`, 0, NULL, 4, 1, 6, 27, + 'KVM memory dynamic scaling capacity', 'Defines the maximum memory capacity in MiB for which VMs can be dynamically scaled to with KVM. The ''kvm.memory.dynamic.scaling.capacity'' setting''s value will be used to define the value of the '''' element of domain XMLs. If it is set to a value less than or equal to ''0'', then the host''s memory capacity will be considered.' +FROM `cloud`.`configuration` `cfg` +WHERE NOT EXISTS (SELECT 1 FROM `cloud`.`configuration` WHERE `name` = 'kvm.memory.dynamic.scaling.capacity') + AND `cfg`.`name` = 'vm.serviceoffering.ram.size.max'; + +-- Creates the 'kvm.cpu.dynamic.scaling.capacity' and, for already active ACS environments, +-- initializes it with the value of the setting 'vm.serviceoffering.cpu.cores.max' +INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `default_value`, `updated`, `scope`, `is_dynamic`, `group_id`, `subgroup_id`, `display_text`, `description`) +SELECT 'Advanced', 'DEFAULT', 'CapacityManager', 'kvm.cpu.dynamic.scaling.capacity', `cfg`.`value`, 0, NULL, 4, 1, 6, 27, + 'KVM CPU dynamic scaling capacity', 'Defines the maximum vCPU capacity for which VMs can be dynamically scaled to with KVM. The ''kvm.cpu.dynamic.scaling.capacity'' setting''s value will be used to define the value of the '''' element of domain XMLs. If it is set to a value less than or equal to ''0'', then the host''s CPU cores capacity will be considered.' +FROM `cloud`.`configuration` `cfg` +WHERE NOT EXISTS (SELECT 1 FROM `cloud`.`configuration` WHERE `name` = 'kvm.cpu.dynamic.scaling.capacity') + AND `cfg`.`name` = 'vm.serviceoffering.cpu.cores.max'; 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 64ec0ed95d2..06b668b801d 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 @@ -124,6 +124,7 @@ import org.libvirt.DomainSnapshot; import org.libvirt.LibvirtException; import org.libvirt.MemoryStatistic; import org.libvirt.Network; +import org.libvirt.SchedLongParameter; import org.libvirt.SchedParameter; import org.libvirt.SchedUlongParameter; import org.libvirt.Secret; @@ -883,6 +884,25 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv SUCCESS, FAILURE, IGNORE } + public enum CpuSchedulerParameter { + CPU_SHARES("cpu_shares"), PERIOD("vcpu_period"), QUOTA("vcpu_quota"); + + private String name; + + CpuSchedulerParameter(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } + } + protected BridgeType bridgeType; protected StorageSubsystemCommandHandler storageHandler; @@ -2965,25 +2985,63 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv protected void setQuotaAndPeriod(VirtualMachineTO vmTO, CpuTuneDef ctd) { if (vmTO.isLimitCpuUse() && vmTO.getCpuQuotaPercentage() != null) { Double cpuQuotaPercentage = vmTO.getCpuQuotaPercentage(); - int period = CpuTuneDef.DEFAULT_PERIOD; - int quota = (int) (period * cpuQuotaPercentage); - if (quota < CpuTuneDef.MIN_QUOTA) { - LOGGER.info("Calculated quota (" + quota + ") below the minimum (" + CpuTuneDef.MIN_QUOTA + ") for VM domain " + vmTO.getUuid() + ", setting it to minimum " + - "and calculating period instead of using the default"); - quota = CpuTuneDef.MIN_QUOTA; - period = (int) ((double) quota / cpuQuotaPercentage); - if (period > CpuTuneDef.MAX_PERIOD) { - LOGGER.info("Calculated period (" + period + ") exceeds the maximum (" + CpuTuneDef.MAX_PERIOD + - "), setting it to the maximum"); - period = CpuTuneDef.MAX_PERIOD; - } - } - ctd.setQuota(quota); - ctd.setPeriod(period); - LOGGER.info("Setting quota=" + quota + ", period=" + period + " to VM domain " + vmTO.getUuid()); + Pair periodAndQuota = getPeriodAndQuota(cpuQuotaPercentage); + ctd.setPeriod(periodAndQuota.first()); + ctd.setQuota(periodAndQuota.second()); + LOGGER.info("Setting quota = [{}] and period = [{}] to VM domain [{}].", periodAndQuota.second(), periodAndQuota.first(), vmTO.getUuid()); } } + /** + * Calculates the CPU period and quota based on the quota percentage defined by the Management Server + * @param cpuQuotaPercentage CPU quota percentage defined by the Management Server + * @return The period and quota to be defined for the VM's domain + */ + protected Pair getPeriodAndQuota(double cpuQuotaPercentage) { + int period = CpuTuneDef.DEFAULT_PERIOD; + long quota = (long) (period * cpuQuotaPercentage); + if (quota < CpuTuneDef.MIN_QUOTA) { + LOGGER.info("Calculated quota ({}) below the minimum ({}), setting it to minimum and calculating period instead of using the default", quota, CpuTuneDef.MIN_QUOTA); + quota = CpuTuneDef.MIN_QUOTA; + period = (int) ((double) quota / cpuQuotaPercentage); + if (period > CpuTuneDef.MAX_PERIOD) { + LOGGER.info("Calculated period ({}) exceeds the maximum ({}), setting it to the maximum", period, CpuTuneDef.MAX_PERIOD); + period = CpuTuneDef.MAX_PERIOD; + } + } + + LOGGER.info("Calculated period = [{}] and quota = [{}] given the [{}] quota percentage.", period, quota, cpuQuotaPercentage); + return new Pair<>(period, quota); + } + + /** + * Dynamically updates the domain's "vcpu_quota" and "period" fields of the CPU tune definition. + * This is required because the values of the fields must change according to the new CPU speed of the VM. + * When the CPU limitation is removed from the domain, the "vcpu_quota" field is set to 17,592,186,044,415. + * @param domain VM's domain. + * @param vmTO VM's transfer object, which contains the required fields to update the "vcpu_quota" and "period" fields. + * @param limitCpuUseChange Indicates whether the CPU limitation for the VM has changed. + * @throws org.libvirt.LibvirtException + **/ + public void updateCpuQuotaAndPeriod(Domain domain, VirtualMachineTO vmTO, boolean limitCpuUseChange) throws LibvirtException { + if (hypervisorLibvirtVersion < MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE || (!limitCpuUseChange && !vmTO.isLimitCpuUse())) { + logger.info("Not updating the [{}] and [{}] for the [{}] domain, because [{}].", + CpuSchedulerParameter.QUOTA, CpuSchedulerParameter.PERIOD, domain.getName(), hypervisorLibvirtVersion < MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE ? + "the current Libvirt version does not support CPU tune" : "it was not requested to remove, change or apply CPU limitation for the instance."); + return; + } + + if (limitCpuUseChange && !vmTO.isLimitCpuUse()) { + logger.info("Updating the [{}] of the [{}] domain to [{}], because CPU limitation has been removed.", CpuSchedulerParameter.QUOTA, domain.getName(), CpuTuneDef.MAX_CPU_QUOTA); + LibvirtComputingResource.setQuota(domain, CpuTuneDef.MAX_CPU_QUOTA); + return; + } + + Pair periodAndQuota = getPeriodAndQuota(vmTO.getCpuQuotaPercentage()); + LibvirtComputingResource.setPeriod(domain, periodAndQuota.first()); + LibvirtComputingResource.setQuota(domain, periodAndQuota.second()); + } + protected void enlightenWindowsVm(VirtualMachineTO vmTO, FeaturesDef features) { if (vmTO.getOs().contains("Windows PV")) { // If OS is Windows PV, then enable the features. Features supported on Windows 2008 and later @@ -3445,10 +3503,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv grd.setMemBalloning(!noMemBalloon); - Long maxRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam()); - - grd.setMemorySize(maxRam); - grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, maxRam)); + long requestedRam = ByteScaleUtils.bytesToKibibytes(vmTO.getRequestedRam()); + long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam()); + grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, requestedRam, minRam)); + grd.setMaxMemory(ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam())); int vcpus = vmTO.getCpus(); Integer maxVcpus = vmTO.getVcpuMaxLimit(); @@ -3459,18 +3517,19 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return grd; } - protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long maxRam) { - long retVal = maxRam; + protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long requestedRam, long minRam) { if (noMemBalloon) { - 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)); - } else if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) { - LOGGER.warn(String.format("Setting System VM's [%s] current memory as max memory [%s].", vmTO.toString(), maxRam)); - } else { - long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam()); - LOGGER.debug(String.format("Setting VM's [%s] current memory as min memory [%s] due to memory ballooning is enabled.", vmTO.toString(), minRam)); - retVal = minRam; + LOGGER.warn("Setting VM's [{}] current memory as requested memory [{}] due to memory ballooning is disabled.", vmTO.toString(), requestedRam); + return requestedRam; } - return retVal; + + if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) { + LOGGER.warn("Setting System VM's [{}] current memory as requested memory [{}].", vmTO.toString(), requestedRam); + return requestedRam; + } + + LOGGER.debug("Setting VM's [{}] current memory as min memory [{}] due to memory ballooning is enabled.", vmTO.toString(), minRam); + return minRam; } /** @@ -6255,29 +6314,59 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv **/ public static Integer getCpuShares(Domain dm) throws LibvirtException { for (SchedParameter c : dm.getSchedulerParameters()) { - if (c.field.equals("cpu_shares")) { + if (c.field.equals(CpuSchedulerParameter.CPU_SHARES.getName())) { return Integer.parseInt(c.getValueAsString()); } } - LOGGER.warn(String.format("Could not get cpu_shares of domain: [%s]. Returning default value of 0. ", dm.getName())); + LOGGER.warn("Could not get [{}] of domain: [{}]. Returning default value of 0. ", CpuSchedulerParameter.CPU_SHARES.getName(), dm.getName()); return 0; } /** - * Sets the cpu_shares (priority) of the running VM
+ * Updates the cpu_shares (priority) of the running VM. * @param dm domain of the VM. * @param cpuShares new priority of the running VM. - * @throws org.libvirt.LibvirtException **/ public static void setCpuShares(Domain dm, Integer cpuShares) throws LibvirtException { + LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.CPU_SHARES.getName(), dm.getName(), cpuShares); SchedUlongParameter[] params = new SchedUlongParameter[1]; params[0] = new SchedUlongParameter(); - params[0].field = "cpu_shares"; + params[0].field = CpuSchedulerParameter.CPU_SHARES.getName(); params[0].value = cpuShares; dm.setSchedulerParameters(params); } + /** + * Updates the period of the running VM. + * @param domain domain of the VM. + * @param period new period of the running VM. + **/ + public static void setPeriod(Domain domain, int period) throws LibvirtException { + LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.PERIOD.getName(), domain.getName(), period); + SchedUlongParameter[] params = new SchedUlongParameter[1]; + params[0] = new SchedUlongParameter(); + params[0].field = CpuSchedulerParameter.PERIOD.getName(); + params[0].value = period; + + domain.setSchedulerParameters(params); + } + + /** + * Updates the quota of the running VM. + * @param domain domain of the VM. + * @param quota new quota of the running VM. + **/ + public static void setQuota(Domain domain, long quota) throws LibvirtException { + LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.QUOTA.getName(), domain.getName(), quota); + SchedLongParameter[] params = new SchedLongParameter[1]; + params[0] = new SchedLongParameter(); + params[0].field = CpuSchedulerParameter.QUOTA.getName(); + params[0].value = quota; + + domain.setSchedulerParameters(params); + } + /** * Set up a libvirt secret for a volume. If Libvirt says that a secret already exists for this volume path, we use its uuid. * The UUID of the secret needs to be prescriptive such that we can register the same UUID on target host during live migration diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index 4d823783a99..e114669b8b5 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -542,7 +542,7 @@ public class LibvirtDomainXMLParser { final String quota = getTagValue("quota", cpuTuneDefElement); if (StringUtils.isNotBlank(quota)) { - cpuTuneDef.setQuota((Integer.parseInt(quota))); + cpuTuneDef.setQuota((Long.parseLong(quota))); } final String period = getTagValue("period", cpuTuneDefElement); 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 097a9b8dd32..bf8b1af6c18 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 @@ -443,15 +443,15 @@ public class LibvirtVMDef { } public static class GuestResourceDef { - private long memory; + private long maxMemory; private long currentMemory = -1; private int vcpu = -1; private int maxVcpu = -1; private boolean memoryBalloning = false; private int memoryBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD); - public void setMemorySize(long mem) { - this.memory = mem; + public void setMaxMemory(long mem) { + this.maxMemory = mem; } public void setCurrentMem(long currMem) { @@ -484,8 +484,8 @@ public class LibvirtVMDef { 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)); + if (this.maxMemory > this.currentMemory) { + response.append(String.format("%s\n", this.maxMemory)); response.append(String.format(" \n", this.maxVcpu - 1, this.currentMemory)); } @@ -1920,11 +1920,12 @@ public class LibvirtVMDef { public static class CpuTuneDef { private int _shares = 0; - private int quota = 0; + private long quota = 0; private int period = 0; static final int DEFAULT_PERIOD = 10000; static final int MIN_QUOTA = 1000; static final int MAX_PERIOD = 1000000; + public static final long MAX_CPU_QUOTA = 17592186044415L; public void setShares(int shares) { _shares = shares; @@ -1934,11 +1935,11 @@ public class LibvirtVMDef { return _shares; } - public int getQuota() { + public long getQuota() { return quota; } - public void setQuota(int quota) { + public void setQuota(long quota) { this.quota = quota; } 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 index 1536984f2e8..b1f64c8c6db 100644 --- 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 @@ -49,10 +49,11 @@ public class LibvirtScaleVmCommandWrapper extends CommandWrapper(1000, 300L)).when(libvirtComputingResourceSpy).getPeriodAndQuota(cpuQuotaPercentage); + Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM"); + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, false); + Mockito.verify(domainMock, Mockito.times(2)).setSchedulerParameters(Mockito.any()); + } + + @Test + public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreUpdatedWhenThereIsCpuCapChangeAndCpuLimitationIsApplied() throws LibvirtException { + Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true); + double cpuQuotaPercentage = 0.03; + Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(cpuQuotaPercentage); + Mockito.doReturn(new Pair<>(1000, 300L)).when(libvirtComputingResourceSpy).getPeriodAndQuota(cpuQuotaPercentage); + Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM"); + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, true); + Mockito.verify(domainMock, Mockito.times(2)).setSchedulerParameters(Mockito.any()); + } + + @Test + public void getPeriodAndQuotaTestAssertQuotaIsEqualToPeriodMultipliedByQuotaPercentage() { + double cpuQuotaPercentage = 0.3; + int expectedPeriod = CpuTuneDef.DEFAULT_PERIOD; + long expectedQuota = (long) (expectedPeriod * cpuQuotaPercentage); + Pair expectedResult = new Pair<>(expectedPeriod, expectedQuota); + Pair result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage); + Assert.assertEquals(expectedResult, result); + } + + @Test + public void getPeriodAndQuotaTestQuotaIsEqualToMinimumWhenRequired() { + double cpuQuotaPercentage = 0.03; + long expectedQuota = CpuTuneDef.MIN_QUOTA; + int expectedPeriod = (int) ((double) expectedQuota / cpuQuotaPercentage); + Pair expectedResult = new Pair<>(expectedPeriod, expectedQuota); + Pair result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage); + Assert.assertEquals(expectedResult, result); + } + + @Test + public void getPeriodAndQuotaTestPeriodIsEqualToMaximumWhenRequired() { + double cpuQuotaPercentage = 0.0003; + long expectedQuota = CpuTuneDef.MIN_QUOTA; + int expectedPeriod = CpuTuneDef.MAX_PERIOD; + Pair expectedResult = new Pair<>(expectedPeriod, expectedQuota); + Pair result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage); + Assert.assertEquals(expectedResult, result); + } } diff --git a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java index 2940f900b08..3be7384ffb7 100644 --- a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java @@ -1214,6 +1214,6 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, public ConfigKey[] getConfigKeys() { return new ConfigKey[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor, StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold, - StorageAllocatedCapacityDisableThresholdForVolumeSize, CapacityCalculateWorkers }; + StorageAllocatedCapacityDisableThresholdForVolumeSize, CapacityCalculateWorkers, KvmMemoryDynamicScalingCapacity, KvmCpuDynamicScalingCapacity }; } } diff --git a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java index 3303bc02933..6c1c3424b1b 100644 --- a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java +++ b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java @@ -20,7 +20,7 @@ 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.capacity.CapacityManager; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.host.HostVO; @@ -44,6 +44,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -54,9 +55,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; 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 @@ -130,30 +129,47 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { * @param vmProfile vm profile */ protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile vmProfile) { - if (to.isLimitCpuUse()) { - VirtualMachine vm = vmProfile.getVirtualMachine(); - HostVO host = hostDao.findById(vm.getHostId()); - if (host == null) { - logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: {}", vm); - return; - } - logger.debug("Limiting CPU usage for VM: {} on host: {}", vm, host); - double hostMaxSpeed = getHostCPUSpeed(host); - double maxSpeed = getVmSpeed(to); - try { - BigDecimal percent = new BigDecimal(maxSpeed / hostMaxSpeed); - percent = percent.setScale(2, RoundingMode.HALF_DOWN); - if (percent.compareTo(new BigDecimal(1)) == 1) { - logger.debug("VM {} CPU MHz exceeded host {} CPU MHz, limiting VM CPU to the host maximum", vm, host); - percent = new BigDecimal(1); - } - to.setCpuQuotaPercentage(percent.doubleValue()); - logger.debug("Host: {} max CPU speed = {} MHz, VM: {} max CPU speed = {} MHz. " + - "Setting CPU quota percentage as: {}", - host, hostMaxSpeed, vm, maxSpeed, percent.doubleValue()); - } catch (NumberFormatException e) { - logger.error("Error calculating VM: {} quota percentage, it will not be set. Error: {}", vm, e.getMessage(), e); + if (!to.isLimitCpuUse()) { + return; + } + + VirtualMachine vm = vmProfile.getVirtualMachine(); + HostVO host = hostDao.findById(vm.getHostId()); + if (host == null) { + logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: [{}].", vm); + return; + } + + logger.debug("Limiting CPU usage for VM: [{}] on host: [{}].", vm, host); + double maxSpeed = getVmSpeed(to); + double hostMaxSpeed = getHostCPUSpeed(host); + Double cpuQuotaPercentage = getCpuQuotaPercentage(maxSpeed, hostMaxSpeed); + if (cpuQuotaPercentage != null) { + to.setCpuQuotaPercentage(cpuQuotaPercentage); + } + } + + /** + * Calculates the VM quota percentage based on the VM and host CPU speeds. + * @param vmSpeeed Speed of the VM. + * @param hostSpeed Speed of the host. + * @return The VM quota percentage. + */ + public Double getCpuQuotaPercentage(double vmSpeeed, double hostSpeed) { + logger.debug("Calculating CPU quota percentage for VM with speed [{}] on host with speed [{}].", vmSpeeed, hostSpeed); + try { + BigDecimal percent = new BigDecimal(vmSpeeed / hostSpeed); + percent = percent.setScale(2, RoundingMode.HALF_DOWN); + if (percent.compareTo(new BigDecimal(1)) > 0) { + logger.debug("VM CPU speed exceeded host CPU speed and, therefore, limiting VM CPU quota to the host maximum."); + percent = new BigDecimal(1); } + double quotaPercentage = percent.doubleValue(); + logger.info("Calculated CPU quota percentage for VM with speed [{}] on host with speed [{}] is [{}].", vmSpeeed, hostSpeed, quotaPercentage); + return quotaPercentage; + } catch (NumberFormatException e) { + logger.info("Could not calculate CPU quota percentage for VM with speed [{}] on host with speed [{}]. Therefore, CPU limitation will not be set for the domain.", vmSpeeed, hostSpeed); + return null; } } @@ -214,28 +230,31 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { Pair max = getHostMaxMemoryAndCpuCores(hostVo, virtualMachine, vmDescription); Long maxHostMemory = max.first(); - Integer maxHostCpuCore = max.second(); + Integer maxHostCpuCores = max.second(); long minMemory = virtualMachineTo.getMinRam(); Long maxMemory = virtualMachineTo.getMaxRam(); - int minCpuCores = virtualMachineTo.getCpus(); - Integer maxCpuCores = minCpuCores; + long requestedMemory = maxMemory; - ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId()); - if (isVmDynamicScalable(serviceOfferingVO, virtualMachineTo, virtualMachine)) { + int minCpuCores = virtualMachineTo.getCpus(); + int maxCpuCores = minCpuCores; + + if (isVmDynamicScalable(virtualMachineTo, virtualMachine)) { + ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId()); serviceOfferingDao.loadDetails(serviceOfferingVO); - maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory); - maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCore); + Long clusterId = hostVo != null ? hostVo.getClusterId() : null; + maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory, clusterId); + maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCores, clusterId); } - virtualMachineTo.setRam(minMemory, maxMemory); + virtualMachineTo.setRam(minMemory, maxMemory, requestedMemory); 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 boolean isVmDynamicScalable(VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) { + return virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId()); } protected Pair getHostMaxMemoryAndCpuCores(HostVO host, VirtualMachine virtualMachine, String vmDescription){ @@ -263,53 +282,34 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { return new Pair<>(maxHostMemory, maxHostCpuCore); } - protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory) { - String serviceOfferingDescription = serviceOfferingVO.toString(); - + protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory, Long clusterId) { Long maxMemory; - Integer customOfferingMaxMemory = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_MEMORY)); - Integer maxMemoryConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.value(); - if (customOfferingMaxMemory != null) { - logger.debug(String.format("Using 'Custom unconstrained' %s max memory value [%sMb] as %s memory.", serviceOfferingDescription, customOfferingMaxMemory, vmDescription)); - maxMemory = ByteScaleUtils.mebibytesToBytes(customOfferingMaxMemory); + ConfigKey maxMemoryConfig = CapacityManager.KvmMemoryDynamicScalingCapacity; + Integer maxMemoryConfigValue = maxMemoryConfig.valueIn(clusterId); + logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] memory.", + serviceOfferingVO.toString(), maxMemoryConfig.key(), maxMemoryConfigValue, clusterId, vmDescription); + if (maxMemoryConfigValue > 0) { + maxMemory = ByteScaleUtils.mebibytesToBytes(maxMemoryConfigValue); } else { - String maxMemoryConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.key(); - - 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.mebibytesToBytes(maxMemoryConfig); - } else { - 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; - } + logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max memory [{}] as VM max memory in the hypervisor.", + maxMemoryConfig.key(), clusterId, vmDescription, maxHostMemory); + maxMemory = maxHostMemory; } return maxMemory; } - protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCore) { - String serviceOfferingDescription = serviceOfferingVO.toString(); - + protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCores, Long clusterId) { 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) { - logger.debug(String.format("Using 'Custom unconstrained' %s max cpu cores [%s] as %s cpu cores.", serviceOfferingDescription, customOfferingMaxCpuCores, vmDescription)); - maxCpuCores = customOfferingMaxCpuCores; + ConfigKey maxCpuCoresConfig = CapacityManager.KvmCpuDynamicScalingCapacity; + Integer maxCpuCoresConfigValue = maxCpuCoresConfig.valueIn(clusterId); + logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] CPU cores.", + serviceOfferingVO.toString(), maxCpuCoresConfig.key(), maxCpuCoresConfigValue, clusterId, vmDescription); + if (maxCpuCoresConfigValue > 0) { + maxCpuCores = maxCpuCoresConfigValue; } else { - String maxCpuCoreConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.key(); - - 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 { - 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; - } + logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max CPU cores [{}] as VM CPU cores in the hypervisor.", + maxCpuCoresConfig.key(), clusterId, vmDescription, maxHostCpuCores); + maxCpuCores = maxHostCpuCores; } return maxCpuCores; } diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index 38cb6d2db46..300803bfa98 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -111,6 +111,10 @@ public interface UserVmManager extends UserVmService { ConfigKey AllowDifferentHostTagsOfferingsForVmScale = new ConfigKey<>("Advanced", Boolean.class, "allow.different.host.tags.offerings.for.vm.scale", "false", "Enables/Disable allowing to change a VM offering to offerings with different host tags", true); + ConfigKey AutoMigrateVmOnLiveScaleInsufficientCapacity = new ConfigKey<>("Advanced", Boolean.class, "auto.migrate.vm.on.live.scale.insufficient.capacity", + "true", "Defines whether a VM should be automatically migrated to a suitable host when the current host " + + "lacks sufficient compute capacity to live scale the instance. Defaults to true.", true, ConfigKey.Scope.Cluster); + static final int MAX_USER_DATA_LENGTH_BYTES = 2048; public static final String CKS_NODE = "cksnode"; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 60482c431eb..0a2f3b902e5 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -2116,25 +2116,26 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir int newCpu = newServiceOffering.getCpu(); int newMemory = newServiceOffering.getRamSize(); int newSpeed = newServiceOffering.getSpeed(); + boolean cpuCapEnabledForTheNewOffering = newServiceOffering.getLimitCpuUse(); int currentCpu = currentServiceOffering.getCpu(); int currentMemory = currentServiceOffering.getRamSize(); int currentSpeed = currentServiceOffering.getSpeed(); + boolean cpuCapEnabledForTheCurrentOffering = currentServiceOffering.getLimitCpuUse(); int memoryDiff = newMemory - currentMemory; int cpuDiff = newCpu * newSpeed - currentCpu * currentSpeed; - // 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)) { - 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); + boolean scalingDown = newSpeed < currentSpeed || newMemory < currentMemory || newCpu < currentCpu; + if (scalingDown) { + throw new InvalidParameterValueException(String.format("Scaling down is not supported while the VM is running. The new service offering attributes " + + "{\"memory\": %s, \"CPU speed\": %s, \"vCPUs\": %s} must not be lower than the current values {\"memory\": %s, \"CPU speed\": %s, \"vCPUs\": %s}.", + newMemory, newSpeed, newCpu, currentMemory, currentSpeed, currentCpu)); } - 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."); - logger.info(message); - throw new InvalidParameterValueException(message); + boolean sameAmountOfResourcesAsThePreviousOffering = newSpeed == currentSpeed && newMemory == currentMemory && newCpu == currentCpu; + boolean cpuCapChange = cpuCapEnabledForTheCurrentOffering != cpuCapEnabledForTheNewOffering; + if (sameAmountOfResourcesAsThePreviousOffering && (vmHypervisorType != HypervisorType.KVM || !cpuCapChange)) { + throw new InvalidParameterValueException("While the VM is running, scaling to a service offering with the same attributes (memory, CPU speed and vCPUs) " + + "is only allowed when the CPU cap is changed."); } serviceOfferingDao.loadDetails(currentServiceOffering); @@ -2200,8 +2201,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir excludes.addHost(vmInstance.getHostId()); } + boolean autoMigrateVmToASuitableHost = AutoMigrateVmOnLiveScaleInsufficientCapacity.valueIn(host.getClusterId()); + if (!existingHostHasCapacity && !autoMigrateVmToASuitableHost) { + logger.error("Unable to scale the VM [{}] because the host [{}] in which it is currently allocated does not " + + "have enough compute capacity to scale the instance and the VM should not be automatically migrated to another host " + + "([{}] setting is [false]).", vmInstance.getInstanceName(), host.getName(), AutoMigrateVmOnLiveScaleInsufficientCapacity.key()); + return false; + } + // #2 migrate the vm if host doesn't have capacity or is in avoid set if (!existingHostHasCapacity) { + logger.info("Host [{}] does not have enough compute capacity to scale the instance [{}]. Since the [{}] setting is " + + "[true], the VM will be migrated to a suitable host and, if succeeded, the VM will be live scaled to the requested " + + "compute offering.", host.getName(), vmInstance.getInstanceName(), AutoMigrateVmOnLiveScaleInsufficientCapacity.key()); _itMgr.findHostAndMigrate(vmInstance.getUuid(), newServiceOfferingId, customParameters, excludes); } @@ -9458,7 +9470,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties, KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction, EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope, - VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale}; + VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale, AutoMigrateVmOnLiveScaleInsufficientCapacity}; } @Override diff --git a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java index d94f9db0c99..e5239a65123 100644 --- a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java +++ b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java @@ -53,6 +53,10 @@ import com.cloud.storage.dao.GuestOSHypervisorDao; import com.cloud.utils.Pair; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; + +import java.math.BigDecimal; +import java.math.RoundingMode; @RunWith(MockitoJUnitRunner.class) public class KVMGuruTest { @@ -186,79 +190,45 @@ public class KVMGuruTest { } @Test - public void validateGetVmMaxMemoryReturnCustomOfferingMaxMemory(){ - int maxCustomOfferingMemory = 64; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(String.valueOf(maxCustomOfferingMemory)); + public void getVmMaxMemoryTestConsiderKvmMemoryDynamicScalingCapacitySettingWhenItIsGreaterThanZero() { + int maxMemoryConfigValue = 64; - long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1l); + ConfigDepotImpl configDepotMock = Mockito.mock(ConfigDepotImpl.class); + ConfigKey.init(configDepotMock); + Mockito.when(configDepotMock.getConfigStringValue(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(String.valueOf(maxMemoryConfigValue)); - Assert.assertEquals(ByteScaleUtils.mebibytesToBytes(maxCustomOfferingMemory), result); + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1L, 1L); + ConfigKey.init(null); + Assert.assertEquals(ByteScaleUtils.mebibytesToBytes(maxMemoryConfigValue), 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.mebibytesToBytes(maxMemoryConfig), result); - } - - @Test - public void validateGetVmMaxMemoryReturnMaxHostMemory(){ + public void getVmMaxMemoryTestConsiderHostMaxMemoryWhenKvmMemoryDynamicScalingCapacitySettingIsEqualToZero() { long maxHostMemory = ByteScaleUtils.mebibytesToBytes(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); + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", maxHostMemory, 1L); 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(){ + public void getVmMaxCpuCoresTestConsiderKvmCpuDynamicScalingCapacitySettingWhenItIsGreaterThanZero() { int maxCpuCoresConfig = 16; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(null); + ConfigDepotImpl configDepotMock = Mockito.mock(ConfigDepotImpl.class); + ConfigKey.init(configDepotMock); + Mockito.when(configDepotMock.getConfigStringValue(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(String.valueOf(maxCpuCoresConfig)); - 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); + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", 1, 1L); + ConfigKey.init(null); Assert.assertEquals(maxCpuCoresConfig, result); } @Test - public void validateGetVmMaxCpuCoresReturnMaxHostMemory(){ + public void getVmMaxCpuCoresTestConsiderHostMaxCpuWhenKvmCpuDynamicScalingCapacitySettingIsEqualToZero() { 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); + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", maxHostCpuCores, 1L); Assert.assertEquals(maxHostCpuCores, result); } @@ -321,39 +291,36 @@ public class KVMGuruTest { 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()); + Mockito.doReturn(true).when(guru).isVmDynamicScalable(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()); + Mockito.verify(guru).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.verify(guru).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()); } @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()); + Mockito.doReturn(false).when(guru).isVmDynamicScalable(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()); + Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()); } @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()); + Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()); } @Test @@ -506,4 +473,30 @@ public class KVMGuruTest { Assert.assertNull(clusterId); } + + @Test + public void getCpuQuotaPercentageTestAssertQuotaEqualsVmSpeedDividedByHostSpeed() { + double hostSpeed = 3000; + double vmSpeed = 500; + double expectedQuota = new BigDecimal(vmSpeed / hostSpeed).setScale(2, RoundingMode.HALF_DOWN).doubleValue(); + double actualQuota = guru.getCpuQuotaPercentage(vmSpeed, hostSpeed); + Assert.assertEquals(expectedQuota, actualQuota, 0.0001); + } + + @Test + public void getCpuQuotaPercentageTestAssertQuotaEqualsOneWhenVmSpeedIsGreaterThanHostSpeed() { + double hostSpeed = 3000; + double vmSpeed = 6000; + double expectedQuota = 1; + double actualQuota = guru.getCpuQuotaPercentage(vmSpeed, hostSpeed); + Assert.assertEquals(expectedQuota, actualQuota, 0.0001); + } + + @Test + public void getCpuQuotaPercentageTestReturnNullWhenANumberFormatExceptionIsThrown() { + double hostSpeed = 0; + double vmSpeed = 6000; + Double actualQuota = guru.getCpuQuotaPercentage(vmSpeed, hostSpeed); + Assert.assertNull(actualQuota); + } } diff --git a/test/integration/component/test_escalations_instances.py b/test/integration/component/test_escalations_instances.py index 89c4f4ce2a6..4aea35850d0 100644 --- a/test/integration/component/test_escalations_instances.py +++ b/test/integration/component/test_escalations_instances.py @@ -3341,9 +3341,6 @@ class TestInstances(cloudstackTestCase): deployed in step1 Step6: Verifying that VM's service offerings is changed """ - if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") # Checking if Dynamic scaling of VM is supported or not list_config = Configurations.list( self.apiClient, diff --git a/test/integration/component/test_escalations_ipaddresses.py b/test/integration/component/test_escalations_ipaddresses.py index b91b67c16c3..d9d760c4901 100644 --- a/test/integration/component/test_escalations_ipaddresses.py +++ b/test/integration/component/test_escalations_ipaddresses.py @@ -3455,8 +3455,7 @@ class TestIpAddresses(cloudstackTestCase): Step18: Verifying Autoscale policy is updated with condition2 """ if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") + self.skipTest("Test not supported on KVM. Skipping it") list_physical_networks = PhysicalNetwork.list( self.apiClient, @@ -3734,8 +3733,7 @@ class TestIpAddresses(cloudstackTestCase): Step16: Verifying that Autoscale VM is updated """ if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") + self.skipTest("Test not supported on KVM. Skipping it") list_physical_networks = PhysicalNetwork.list( self.apiClient, @@ -4061,8 +4059,7 @@ class TestIpAddresses(cloudstackTestCase): Step14: Enabling Autoscale VM group and verifying it was enabled """ if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") + self.skipTest("Test not supported on KVM. Skipping it.") list_physical_networks = PhysicalNetwork.list( self.apiClient, diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json index bb90661d9d3..566e182b8fa 100644 --- a/ui/public/locales/el_GR.json +++ b/ui/public/locales/el_GR.json @@ -2270,8 +2270,6 @@ "message.error.enable.saml": "Δεν είναι δυνατή η εύρεση των id χρηστών για την ενεργοποίηση του Saml σύνδεση μιας φοράς, παρακαλούμε να το ενεργοποιήσετε με μη αυτόματο τρόπο.", "message.error.end.date.and.time": "Παρακαλώ, εισάγετε την τελική ημερομηνία και ώρα!", "message.error.endip": "Πληκτρολογήστε End IP", -"message.error.fixed.offering.kvm": "Δεν είναι δυνατό να κλιμωκαθούν οι εικονές μηχανές που χρησιμοποιούν τον επόπτη KVM με ένα σταθερό υπολογισμό προσφοράς υπηρεσίας.", -"message.error.fixed.offering.kvm": "Δεν είναι εφικτή η κλιμάκωση προς τα πάνω της εικονικής μηχανής ενώ ανήκει σε μία σταθερή προσφορά νέφους.", "message.error.gateway": "Πληκτρολογήστε Πύλη", "message.error.host.name": "Πληκτρολογήστε το όνομα του κεντρικού υπολογιστή", "message.error.host.password": "Πληκτρολογήστε τον κωδικό πρόσβασης κεντρικού υπολογιστή", diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 1187b3e62b4..83422cd4101 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -3599,7 +3599,6 @@ "message.error.zone.name": "Please enter Zone name.", "message.error.zone.type": "Please select Zone type.", "message.error.linstor.resourcegroup": "Please enter the Linstor Resource-Group.", -"message.error.fixed.offering.kvm": "It's not possible to scale up Instances that utilize KVM hypervisor with a fixed compute offering.", "message.error.create.webhook.local.account": "Account must be provided for creating a Webhook with Local scope.", "message.error.create.webhook.name": "Name must be provided for creating a Webhook.", "message.error.create.webhook.payloadurl": "Payload URL must be provided for creating a Webhook.", diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json index 850264fb341..b7f32d7a2e1 100644 --- a/ui/public/locales/ja_JP.json +++ b/ui/public/locales/ja_JP.json @@ -2932,7 +2932,6 @@ "message.error.domain": "ドメインを入力し、ROOTドメインは空のままにします", "message.error.enable.saml": "SAML SSOを有効にするユーザーIDが見つかりません。手動で有効にしてください。", "message.error.endip": "終了IPを入力してください", - "message.error.fixed.offering.kvm": "固定コンピューティングオファリングでKVMハイパーバイザーを利用するVMをスケールアップすることはできません。", "message.error.gateway": "ゲートウェイに入ってください", "message.error.host.name": "ホスト名を入力してください", "message.error.host.password": "ホストパスワードを入力してください", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 4b446eccff3..dee353ec589 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -3229,7 +3229,6 @@ "message.error.zone.name": "Por favor, insira o nome da zona", "message.error.zone.type": "Por favor, selecione o tipo de zona", "message.error.linstor.resourcegroup": "Por favor, insira o Linstor Resource-Group", -"message.error.fixed.offering.kvm": "N\u00e3o \u00e9 poss\u00edvel escalar VMs que utilizam o virtualizador KVM com uma oferta de computa\u00e7\u00e3o fixa.", "message.fail.to.delete": "Falha ao deletar.", "message.failed.to.add": "Falha ao adicionar", "message.failed.to.assign.vms": "Falha ao atribuir VMs", diff --git a/ui/public/locales/te.json b/ui/public/locales/te.json index ed72626e0a6..f3b4c70ca2e 100644 --- a/ui/public/locales/te.json +++ b/ui/public/locales/te.json @@ -3169,7 +3169,6 @@ "message.error.zone.name": "దయచేసి జోన్ పేరును నమోదు చేయండి.", "message.error.zone.type": "దయచేసి జోన్ రకాన్ని ఎంచుకోండి.", "message.error.linstor.resourcegroup": "దయచేసి Linstor Resource-Groupని నమోదు చేయండి.", - "message.error.fixed.offering.kvm": "ఫిక్స్‌డ్ కంప్యూట్ ఆఫర్‌తో KVM హైపర్‌వైజర్‌ను ఉపయోగించుకునే సందర్భాలను స్కేల్ చేయడం సాధ్యం కాదు.", "message.error.create.webhook.local.account": "స్థానిక స్కోప్‌తో వెబ్‌హుక్‌ని సృష్టించడానికి తప్పనిసరిగా ఖాతా అందించబడాలి.", "message.error.create.webhook.name": "వెబ్‌హుక్‌ని సృష్టించడానికి తప్పనిసరిగా పేరు అందించాలి.", "message.error.create.webhook.payloadurl": "Webhookని సృష్టించడానికి పేలోడ్ URL తప్పనిసరిగా అందించబడాలి.", diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json index 4b68a2ef6d7..9536bab7ee4 100644 --- a/ui/public/locales/zh_CN.json +++ b/ui/public/locales/zh_CN.json @@ -3462,7 +3462,6 @@ "message.error.zone.name": "\u8BF7\u8F93\u5165\u533A\u57DF\u540D\u79F0", "message.error.zone.type": "\u8BF7\u9009\u62E9\u533A\u57DF\u7C7B\u578B", "message.error.linstor.resourcegroup": "\u8BF7\u8F93\u5165 Linstor \u8D44\u6E90\u7EC4", - "message.error.fixed.offering.kvm": "\u4E0D\u53EF\u80FD\u901A\u8FC7\u56FA\u5B9A\u7684\u8BA1\u7B97\u65B9\u6848\u6765\u6269\u5C55KVM\u865A\u62DF\u673A\u3002", "message.fail.to.delete": "\u5220\u9664\u5931\u8D25\u3002", "message.failed.to.add": "\u6DFB\u52A0\u5931\u8D25", "message.failed.to.assign.vms": "\u672A\u80FD\u5206\u914D\u865A\u62DF\u673A", diff --git a/ui/src/views/compute/ScaleVM.vue b/ui/src/views/compute/ScaleVM.vue index ce8dca5b43a..b810e1327c6 100644 --- a/ui/src/views/compute/ScaleVM.vue +++ b/ui/src/views/compute/ScaleVM.vue @@ -23,10 +23,6 @@ - - - - offering.id === this.resource.serviceofferingid) - this.currentOffer = this.offerings[0] - if (this.currentOffer === undefined) { - this.fixedOfferingKvm = true - } - } - this.offerings.map(i => { this.offeringsMap[i.id] = i }) + this.offerings.forEach(offering => { this.offeringsMap[offering.id] = offering }) }).finally(() => { this.loading = false }) }, getMinCpu () { - // We can only scale up while a VM is running - if (this.resource.state === 'Running') { - return this.resource.cpunumber - } return this.selectedOffering?.serviceofferingdetails?.mincpunumber * 1 || 1 }, - getMinMemory () { - // We can only scale up while a VM is running - if (this.resource.state === 'Running') { - return this.resource.memory + getInitialCpuValue () { + const offeringMinCpu = this.getMinCpu() + if (this.resource.cpunumber < offeringMinCpu) { + return offeringMinCpu } + return this.resource.cpunumber + }, + getMinMemory () { return this.selectedOffering?.serviceofferingdetails?.minmemory * 1 || 32 }, + getInitialMemoryValue () { + const offeringMinMemory = this.getMinMemory() + if (this.resource.memory < offeringMinMemory) { + return offeringMinMemory + } + return this.resource.memory + }, getCPUSpeed () { // We can only scale up while a VM is running if (this.resource.state === 'Running') { diff --git a/ui/src/views/compute/wizard/ComputeSelection.vue b/ui/src/views/compute/wizard/ComputeSelection.vue index 563e17984e3..9cf36153f5c 100644 --- a/ui/src/views/compute/wizard/ComputeSelection.vue +++ b/ui/src/views/compute/wizard/ComputeSelection.vue @@ -111,6 +111,10 @@ export default { type: Number, default: 0 }, + initialCpuValue: { + type: Number, + default: 0 + }, minCpu: { type: Number, default: 0 @@ -119,6 +123,10 @@ export default { type: Number, default: 2 }, + initialMemoryValue: { + type: Number, + default: 0 + }, minMemory: { type: Number, default: 0 @@ -200,8 +208,8 @@ export default { }, methods: { fillValue () { - this.cpuNumberInputValue = this.minCpu - this.memoryInputValue = this.minMemory + this.cpuNumberInputValue = this.initialCpuValue > 0 ? this.initialCpuValue : this.minCpu + this.memoryInputValue = this.initialMemoryValue > 0 ? this.initialMemoryValue : this.minMemory this.cpuSpeedInputValue = this.cpuSpeed if (!this.preFillContent) {