Live scaling for VMs with fixed service offerings on KVM (#12975)

This commit is contained in:
Bernardo De Marco Gonçalves 2026-05-22 08:19:21 -03:00 committed by GitHub
parent 850b44317a
commit 6b831f5196
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 504 additions and 267 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -133,6 +133,20 @@ public interface CapacityManager {
"capacity.calculate.workers", "1",
"Number of worker threads to be used for capacities calculation", true);
ConfigKey<Integer> 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 " +
"'<maxMemory />' 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<Integer> 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 " +
"'<vcpu />' 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);

View File

@ -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);
}

View File

@ -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 ''<maxMemory />'' 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 ''<vcpu />'' 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';

View File

@ -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<Integer, Long> 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<Integer, Long> 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<Integer, Long> 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 <br/>
* 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

View File

@ -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);

View File

@ -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("<memory>%s</memory>\n", this.currentMemory));
response.append(String.format("<currentMemory>%s</currentMemory>\n", this.currentMemory));
if (this.memory > this.currentMemory) {
response.append(String.format("<maxMemory slots='16' unit='KiB'>%s</maxMemory>\n", this.memory));
if (this.maxMemory > this.currentMemory) {
response.append(String.format("<maxMemory slots='16' unit='KiB'>%s</maxMemory>\n", this.maxMemory));
response.append(String.format("<cpu> <numa> <cell id='0' cpus='0-%s' memory='%s' unit='KiB'/> </numa> </cpu>\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;
}

View File

@ -49,10 +49,11 @@ public class LibvirtScaleVmCommandWrapper extends CommandWrapper<ScaleVmCommand,
conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
Domain dm = conn.domainLookupByName(vmName);
logger.debug(String.format("Scaling %s.", scalingDetails));
logger.debug("Scaling {}.", scalingDetails);
scaleMemory(dm, newMemory, vmDefinition);
scaleVcpus(dm, newVcpus, vmDefinition);
updateCpuShares(dm, newCpuShares);
libvirtComputingResource.updateCpuQuotaAndPeriod(dm, vmSpec, command.getLimitCpuUseChange());
return new ScaleVmAnswer(command, true, String.format("Successfully scaled %s.", scalingDetails));
} catch (LibvirtException | CloudRuntimeException e) {
@ -74,7 +75,7 @@ public class LibvirtScaleVmCommandWrapper extends CommandWrapper<ScaleVmCommand,
if (oldCpuShares < newCpuShares) {
LibvirtComputingResource.setCpuShares(dm, newCpuShares);
logger.info(String.format("Successfully increased cpu_shares of VM [%s] from [%s] to [%s].", dm.getName(), oldCpuShares, newCpuShares));
logger.info("Successfully increased cpu_shares of VM [{}] from [{}] to [{}].", dm.getName(), oldCpuShares, newCpuShares);
}
}

View File

@ -5642,35 +5642,45 @@ public class LibvirtComputingResourceTest {
Mockito.verify(vmDef, times(1)).addComp(any());
}
public void validateGetCurrentMemAccordingToMemBallooningWithoutMemBalooning(){
@Test
public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryWithoutMemBallooning(){
VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTo.getType()).thenReturn(Type.User);
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
libvirtComputingResource.noMemBalloon = true;
long maxMemory = 2048;
long requestedMemory = 1024 * 1024;
long minMemory = 512 * 1024;
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory);
Assert.assertEquals(maxMemory, currentMemory);
Mockito.verify(vmTo, Mockito.times(0)).getMinRam();
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory);
Assert.assertEquals(requestedMemory, currentMemory);
}
@Test
public void validateGetCurrentMemAccordingToMemBallooningWithtMemBalooning(){
public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryWithMemoryBallooning(){
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
libvirtComputingResource.noMemBalloon = false;
long maxMemory = 2048;
long minMemory = ByteScaleUtils.mebibytesToBytes(64);
VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTo.getType()).thenReturn(Type.User);
Mockito.when(vmTo.getMinRam()).thenReturn(minMemory);
long requestedMemory = 1024 * 1024;
long minMemory = 512 * 1024;
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory);
Assert.assertEquals(ByteScaleUtils.bytesToKibibytes(minMemory), currentMemory);
Mockito.verify(vmTo).getMinRam();
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory);
Assert.assertEquals(minMemory, currentMemory);
}
@Test
public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryForSystemVms() {
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
libvirtComputingResource.noMemBalloon = false;
VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTo.getType()).thenReturn(Type.SecondaryStorageVm);
long requestedMemory = 1024 * 1024;
long minMemory = 512 * 1024;
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory);
Assert.assertEquals(requestedMemory, currentMemory);
}
@Test
public void validateCreateGuestResourceDefWithVcpuMaxLimit(){
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
@ -7234,4 +7244,82 @@ public class LibvirtComputingResourceTest {
libvirtComputingResourceSpy.getInterface(connMock, vmName, invalidMacAddress);
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreNotUpdatedWhenLibvirtVersionIsLessThanTheMinimum() throws LibvirtException {
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 8999;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, null, false);
Mockito.verify(domainMock, Mockito.never()).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreNotUpdatedWhenThereIsNoCapCapChangeAndNoCpuLimitationIsApplied() throws LibvirtException {
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false);
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, false);
Mockito.verify(domainMock, Mockito.never()).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertQuotaIsRemovedWhenThereIsCpuCapChangeAndNoCpuLimitationIsApplied() throws LibvirtException {
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false);
Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM");
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, true);
Mockito.verify(domainMock, Mockito.times(1)).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreUpdatedWhenThereIsNotCpuCapChangeAndCpuLimitationIsApplied() 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, 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<Integer, Long> expectedResult = new Pair<>(expectedPeriod, expectedQuota);
Pair<Integer, Long> 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<Integer, Long> expectedResult = new Pair<>(expectedPeriod, expectedQuota);
Pair<Integer, Long> 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<Integer, Long> expectedResult = new Pair<>(expectedPeriod, expectedQuota);
Pair<Integer, Long> result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage);
Assert.assertEquals(expectedResult, result);
}
}

View File

@ -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 };
}
}

View File

@ -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<Long, Integer> 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<Long, Integer> 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<Integer> 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<Integer> 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;
}

View File

@ -111,6 +111,10 @@ public interface UserVmManager extends UserVmService {
ConfigKey<Boolean> 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<Boolean> 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";

View File

@ -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

View File

@ -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<Integer> 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<Integer> 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<Integer> 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<Integer> 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);
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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": "Πληκτρολογήστε τον κωδικό πρόσβασης κεντρικού υπολογιστή",

View File

@ -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.",

View File

@ -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": "ホストパスワードを入力してください",

View File

@ -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",

View File

@ -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 తప్పనిసరిగా అందించబడాలి.",

View File

@ -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",

View File

@ -23,10 +23,6 @@
<loading-outlined style="color: #1890ff;" />
</div>
<a-alert v-if="fixedOfferingKvm" type="error" show-icon>
<template #message><span style="margin-bottom: 5px" v-html="$t('message.error.fixed.offering.kvm')" /></template>
</a-alert>
<compute-offering-selection
:compute-items="offerings"
:loading="loading"
@ -42,9 +38,11 @@
:memory-input-decorator="memoryKey"
:computeOfferingId="selectedOffering.id"
:isConstrained="'serviceofferingdetails' in selectedOffering"
:initialCpuValue="getInitialCpuValue()"
:minCpu="getMinCpu()"
:maxCpu="'serviceofferingdetails' in selectedOffering ? selectedOffering.serviceofferingdetails.maxcpunumber*1 : Number.MAX_SAFE_INTEGER"
:cpuSpeed="getCPUSpeed()"
:initialMemoryValue="getInitialMemoryValue()"
:minMemory="getMinMemory()"
:maxMemory="'serviceofferingdetails' in selectedOffering ? selectedOffering.serviceofferingdetails.maxmemory*1 : Number.MAX_SAFE_INTEGER"
:isCustomized="selectedOffering.iscustomized"
@ -151,32 +149,31 @@ export default {
return
}
this.offerings = response.listserviceofferingsresponse.serviceoffering || []
if (this.resource.state === 'Running' && this.resource.hypervisor === 'KVM') {
this.offerings = this.offerings.filter(offering => 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') {

View File

@ -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) {