Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle many vCPUs -- backport #315

Merged
merged 2 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ public static Stream<MockConfigDescriptor<?>> mockConfiguration() {
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpuPerSocket, version, 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfThreadsPerCpu, version, 8),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, version, createMaxNumberOfVmCpusMap()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpusCoefficient, 2),
MockConfigDescriptor.of(ConfigValues.ManyVmCpus, 128),
MockConfigDescriptor.of(ConfigValues.UefiBigVmMemoryGB, 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets, version, 16),
MockConfigDescriptor.of(ConfigValues.VM32BitMaxMemorySizeInMB, version, 20480),
MockConfigDescriptor.of(ConfigValues.VM64BitMaxMemorySizeInMB, version, 4194304),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public static Stream<MockConfigDescriptor<?>> mockConfiguration() {

MockConfigDescriptor.of(ConfigValues.VM64BitMaxMemorySizeInMB, Version.getLast(), MAX_MEM),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, Version.getLast(), createMaxNumberOfVmCpusMap()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpusCoefficient, 2),
MockConfigDescriptor.of(ConfigValues.ManyVmCpus, 128),
MockConfigDescriptor.of(ConfigValues.UefiBigVmMemoryGB, 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets, Version.getLast(), 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpuPerSocket, Version.getLast(), 254),
MockConfigDescriptor.of(ConfigValues.MaxNumOfThreadsPerCpu, Version.getLast(), 8),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ public static Stream<MockConfigDescriptor<?>> mockConfiguration() {
return Stream.of(
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, COMPAT_VERSION_FOR_CPU_SOCKET_TEST,
createMaxNumberOfVmCpusMap()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpusCoefficient, 2),
MockConfigDescriptor.of(ConfigValues.ManyVmCpus, 128),
MockConfigDescriptor.of(ConfigValues.UefiBigVmMemoryGB, 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets,
COMPAT_VERSION_FOR_CPU_SOCKET_TEST,
MAX_NUM_SOCKETS),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ public static Stream<MockConfigDescriptor<?>> mockConfiguration() {
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets, Version.v4_3, 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, Version.v4_3, createMaxNumberOfVmCpusMap()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets, Version.getLast(), 16),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, Version.getLast(), createMaxNumberOfVmCpusMap())
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, Version.getLast(), createMaxNumberOfVmCpusMap()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpusCoefficient, 2),
MockConfigDescriptor.of(ConfigValues.ManyVmCpus, 128),
MockConfigDescriptor.of(ConfigValues.UefiBigVmMemoryGB, 16)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ public enum ConfigValues {
@TypeConverterAttribute(Integer.class)
MaxNumOfCpusCoefficient(ClientAccessLevel.User),
@TypeConverterAttribute(Integer.class)
ManyVmCpus(ClientAccessLevel.User),
@TypeConverterAttribute(Integer.class)
UefiBigVmMemoryGB(ClientAccessLevel.User),
@TypeConverterAttribute(Integer.class)
NumberVmRefreshesBeforeSave,
@TypeConverterAttribute(Integer.class)
NumberVdsRefreshesBeforeTryToStartUnknownVms,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,20 @@ public static Integer calcMaxVCpu(VmBase vm, Version compatibilityVersion, Archi

int threadsPerCore = vm.getThreadsPerCpu();
int cpuPerSocket = vm.getCpuPerSocket();
if (compatibilityVersion.greaterOrEquals(Version.v4_6) && maxSockets > LEGACY_MAX_SOCKETS) {
// Maximum vCPUs, even when unused, take some memory from the RAM available to
// the guest OS.
// So it's desirable not to set the maximum number of vCPUs unnecessarily high,
// in order to not waste memory. This concerns primarily configurations with a
// limit of CPU sockets higher than the one of 16, used for ages and no longer
// needed.
Integer maxVCpusCoefficient = Config.getValue(ConfigValues.MaxNumOfCpusCoefficient);
if (compatibilityVersion.greaterOrEquals(Version.v4_6)) {
// Maximum vCPUs, even when unused, take some memory from
// the RAM available to the guest OS. Moreover, many vCPUs
// can possibly cause other problems (see e.g.
// https://bugzilla.redhat.com/2074149). That means it's
// better not to set the maximum number of vCPUs
// unnecessarily high.
final Integer maxVCpusCoefficient = Config.getValue(ConfigValues.MaxNumOfCpusCoefficient);
final int threadsPerSocket = cpuPerSocket * threadsPerCore;
final int maxComputedSockets = Math.min(maxSockets,
maxVCpusCoefficient * vm.getNumOfCpus() / threadsPerSocket);
maxVCpus = Math.min(maxVCpus, Math.max(maxComputedSockets, LEGACY_MAX_SOCKETS) * threadsPerSocket);
final Integer manyVmCpus = Config.getValue(ConfigValues.ManyVmCpus);
final int maxLegacySockets = Math.min(LEGACY_MAX_SOCKETS, (manyVmCpus - 1) / threadsPerSocket);
maxVCpus = Math.min(maxVCpus, Math.max(maxComputedSockets, maxLegacySockets) * threadsPerSocket);
}
final BiosType biosType = vm.getBiosType();
return calcMaxVCpu(architectureFamily, maxSockets, maxVCpus, threadsPerCore, cpuPerSocket, biosType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public static Stream<MockConfigDescriptor<?>> mockConfiguration() {
return Stream.of(
MockConfigDescriptor.of(ConfigValues.VdcVersion, Version.getLast().getValue()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmCpus, Version.getLast(), createMaxNumberOfVmCpusMap()),
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets, Version.getLast(), 4)
MockConfigDescriptor.of(ConfigValues.MaxNumOfVmSockets, Version.getLast(), 4),
MockConfigDescriptor.of(ConfigValues.MaxNumOfCpusCoefficient, 2),
MockConfigDescriptor.of(ConfigValues.ManyVmCpus, 128),
MockConfigDescriptor.of(ConfigValues.UefiBigVmMemoryGB, 16)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,8 @@ private void writeFeatures() {
boolean acpiEnabled = vm.getAcpiEnable();
boolean kaslrEnabled = vmInfoBuildUtils.isKASLRDumpEnabled(vm.getVmOsId());
boolean secureBootEnabled = vm.getBiosType() == BiosType.Q35_SECURE_BOOT;
if (!acpiEnabled && !hypervEnabled && !kaslrEnabled && !secureBootEnabled) {
Integer tsegSize = vmInfoBuildUtils.tsegSizeMB(vm, hostDevicesSupplier);
if (!acpiEnabled && !hypervEnabled && !kaslrEnabled && !secureBootEnabled && tsegSize == null) {
return;
}

Expand Down Expand Up @@ -858,9 +859,15 @@ private void writeFeatures() {
writer.writeElement("vmcoreinfo");
}

if (secureBootEnabled) {
if (secureBootEnabled || tsegSize != null) {
writer.writeStartElement("smm");
writer.writeAttributeString("state", "on");
if (tsegSize != null) {
writer.writeStartElement("tseg");
writer.writeAttributeString("unit", "MiB");
writer.writeRaw(tsegSize.toString());
writer.writeEndElement();
}
writer.writeEndElement();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ public class VmInfoBuildUtils {
Pattern.compile(String.format(BLOCK_DOMAIN_DISK_PATH, ValidationUtils.GUID,
ValidationUtils.GUID, ValidationUtils.GUID));

private static final int DEFAULT_TSEG_SIZE_MB = 16;
private static final int MANY_VCPUS_TSEG_SIZE_INCREASE_MB = 24;
private static final int PER_TB_TSEG_SIZE_INCREASE_MB = 8;

public static final int NVDIMM_LABEL_SIZE = 128 * 1024;

@Inject
Expand Down Expand Up @@ -1677,4 +1681,46 @@ public static boolean isVmWithHighNumberOfX86Vcpus(VM vm) {
return vm.getClusterArch().getFamily() == ArchitectureType.x86
&& maxNumberOfVcpus(vm) >= VmCpuCountHelper.HIGH_NUMBER_OF_X86_VCPUS;
}

/**
* Return a recommended TSEG size in MB or null to use the default value.
*
* Large VMs with UEFI will not start if they don't have TSEG large enough.
* There is no rule what "large" is, it's a VM with "too many" vCPUs or "too large" memory.
* There are just some suggestions:
* - To use 48 MB for VMs with many vCPUs or large memory:
* https://bugzilla.redhat.com/show_bug.cgi?id=2074149#c22
* - The same in a different context:
* https://bugzilla.redhat.com/show_bug.cgi?id=1469338#c30
* - To increase the size by unspecified amount for many vCPUs and by 8 MB of each TB of
* address space (which is max RAM + NVDIMM size in our case):
* https://bugzilla.redhat.com/show_bug.cgi?id=1469338#c7
* - Confirmation that 48 MB may not be enough for really large VMs:
* https://bugzilla.redhat.com/show_bug.cgi?id=2074149#c28
**/
public Integer tsegSizeMB(VM vm, MemoizingSupplier<Map<String, HostDevice>> hostDevicesSupplier) {
// If UEFI is not used, we should be safe and use the default value.
if (vm.getBiosType() == null || !vm.getBiosType().isOvmf()) {
return null;
}
// If the VM is small, we also use the default value, because the TSEG size is taken
// from the memory available to the guest.
final int cpuLimit = Config.getValue(ConfigValues.ManyVmCpus);
final int smallMemory = Config.<Integer> getValue(ConfigValues.UefiBigVmMemoryGB) * 1024;
if (maxNumberOfVcpus(vm) < cpuLimit && vm.getMaxMemorySizeMb() < smallMemory) {
return null;
}
// Otherwise, we start with the default value (16 MB currently) and add 24 MB
// (which implies at least the recommended value of 48 MB together with the default value
// and additional value per memory) for VMs with many vCPUs and 8 MB per each (even partial)
// TB of RAM. This is probably a bit wasteful but it's better than risking the VM won't
// start.
int size = DEFAULT_TSEG_SIZE_MB;
if (maxNumberOfVcpus(vm) >= cpuLimit) {
size += MANY_VCPUS_TSEG_SIZE_INCREASE_MB;
}
final long memorySize = vm.getMaxMemorySizeMb() + getNvdimmTotalSize(vm, hostDevicesSupplier) / (1024 * 1024);
size += (memorySize / (1024 * 1024) + 1) * PER_TB_TSEG_SIZE_INCREASE_MB;
return size;
}
}
2 changes: 2 additions & 0 deletions packaging/dbscripts/upgrade/pre_upgrade/0000_config.sql
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ select fn_db_add_config_value_for_versions_up_to('MaxNumOfVmCpus', '{"x86":710,"
select fn_db_add_config_value_for_versions_up_to('MaxNumOfVmSockets', '16', '4.5');
select fn_db_add_config_value_for_versions_up_to('MaxNumOfVmSockets', '10000', '4.7');
select fn_db_add_config_value('MaxNumOfCpusCoefficient', '2', 'general');
select fn_db_add_config_value('ManyVmCpus', '128', 'general');
select fn_db_add_config_value('UefiBigVmMemoryGB', '16', 'general');
select fn_db_add_config_value('MaxRerunVmOnVdsCount','3','general');
select fn_db_add_config_value('MaxStorageVdsDelayCheckSec','5','general');
select fn_db_add_config_value('MaxStorageVdsTimeoutCheckSec','30','general');
Expand Down
4 changes: 4 additions & 0 deletions packaging/etc/engine-config/engine-config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ MaxNumOfVmSockets.description="Max Number of Socket per Virtual Machine; depreca
MaxNumOfVmSockets.type=Integer
MaxNumOfCpusCoefficient.description="The factor between max VM CPUs and configured VM CPUs; effective since compatibility level 4.6"
MaxNumOfCpusCoefficient.type=Integer
ManyVmCpus.description="How many vCPUs are considered to be many in maximum vCPU number calculations"
ManyVmCpus.type=Integer
UefiBigVmMemoryGB.description="How much UEFI VM memory is considered big enough when changing TSEG size"
UefiBigVmMemoryGB.type=Integer
MaxRerunVmOnVdsCount.description="Max Virtual Machine Rerun Attempts on a Host"
MaxRerunVmOnVdsCount.type=Integer
MaxSchedulerWeight.description="Max weight score for a single scheduler weight module"
Expand Down