Merge branch '4.22'

This commit is contained in:
Daan Hoogland 2026-01-09 15:00:05 +01:00
commit 2811217578
7 changed files with 124 additions and 19 deletions

View File

@ -55,6 +55,9 @@ public interface VmDetailConstants {
String NIC_MULTIQUEUE_NUMBER = "nic.multiqueue.number"; String NIC_MULTIQUEUE_NUMBER = "nic.multiqueue.number";
String NIC_PACKED_VIRTQUEUES_ENABLED = "nic.packed.virtqueues.enabled"; String NIC_PACKED_VIRTQUEUES_ENABLED = "nic.packed.virtqueues.enabled";
// KVM specific, disk controllers
String KVM_SKIP_FORCE_DISK_CONTROLLER = "skip.force.disk.controller";
// Mac OSX guest specific (internal) // Mac OSX guest specific (internal)
String SMC_PRESENT = "smc.present"; String SMC_PRESENT = "smc.present";
String FIRMWARE = "firmware"; String FIRMWARE = "firmware";

View File

@ -213,6 +213,8 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
private static final String LEFT_JOIN_VM_TEMPLATE = "LEFT JOIN vm_template ON vm_template.id = vi.vm_template_id "; private static final String LEFT_JOIN_VM_TEMPLATE = "LEFT JOIN vm_template ON vm_template.id = vi.vm_template_id ";
private static final String STORAGE_POOLS_WITH_CHILDREN = "SELECT DISTINCT parent FROM storage_pool WHERE parent != 0 AND removed IS NULL";
public CapacityDaoImpl() { public CapacityDaoImpl() {
_hostIdTypeSearch = createSearchBuilder(); _hostIdTypeSearch = createSearchBuilder();
_hostIdTypeSearch.and("hostId", _hostIdTypeSearch.entity().getHostOrPoolId(), SearchCriteria.Op.EQ); _hostIdTypeSearch.and("hostId", _hostIdTypeSearch.entity().getHostOrPoolId(), SearchCriteria.Op.EQ);
@ -379,6 +381,11 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
finalQuery.append(" AND capacity_type = ?"); finalQuery.append(" AND capacity_type = ?");
resourceIdList.add(capacityType.longValue()); resourceIdList.add(capacityType.longValue());
} }
// Exclude storage pools with children from capacity calculations to avoid double counting
finalQuery.append(" AND NOT (capacity.capacity_type = ").append(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED)
.append(" AND capacity.host_id IN (").append(STORAGE_POOLS_WITH_CHILDREN).append("))");
if (CollectionUtils.isNotEmpty(hostIds)) { if (CollectionUtils.isNotEmpty(hostIds)) {
finalQuery.append(String.format(" AND capacity.host_id IN (%s)", StringUtils.join(hostIds, ","))); finalQuery.append(String.format(" AND capacity.host_id IN (%s)", StringUtils.join(hostIds, ",")));
if (capacityType == null) { if (capacityType == null) {
@ -541,6 +548,10 @@ public class CapacityDaoImpl extends GenericDaoBase<CapacityVO, Long> implements
StringBuilder sql = new StringBuilder(LIST_CAPACITY_GROUP_BY_CAPACITY_PART1); StringBuilder sql = new StringBuilder(LIST_CAPACITY_GROUP_BY_CAPACITY_PART1);
List<Long> resourceIdList = new ArrayList<Long>(); List<Long> resourceIdList = new ArrayList<Long>();
// Exclude storage pools with children from capacity calculations to avoid double counting
sql.append(" AND NOT (capacity.capacity_type = ").append(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED)
.append(" AND capacity.host_id IN (").append(STORAGE_POOLS_WITH_CHILDREN).append("))");
if (zoneId != null) { if (zoneId != null) {
sql.append(" AND capacity.data_center_id = ?"); sql.append(" AND capacity.data_center_id = ?");
resourceIdList.add(zoneId); resourceIdList.add(zoneId);

View File

@ -3493,6 +3493,44 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return useBLOCKDiskType(physicalDisk) ? DiskDef.DiskType.BLOCK : DiskDef.DiskType.FILE; return useBLOCKDiskType(physicalDisk) ? DiskDef.DiskType.BLOCK : DiskDef.DiskType.FILE;
} }
/**
* Defines the disk configuration for the default pool type based on the provided parameters.
* It determines the appropriate disk settings depending on whether the disk is a data disk, whether
* it's a Windows template, whether UEFI is enabled, and whether secure boot is active.
*
* @param disk The disk definition object that will be configured with the disk settings.
* @param volume The volume (disk) object, containing information about the type of disk.
* @param isWindowsTemplate Flag indicating whether the template is a Windows template.
* @param isUefiEnabled Flag indicating whether UEFI is enabled.
* @param isSecureBoot Flag indicating whether secure boot is enabled.
* @param physicalDisk The physical disk object that contains the path to the disk.
* @param devId The device ID for the disk.
* @param diskBusType The disk bus type to use if not skipping force disk controller.
* @param diskBusTypeData The disk bus type to use for data disks, if applicable.
* @param details A map of VM details containing additional configuration values, such as whether to skip force
* disk controller.
*/
protected void defineDiskForDefaultPoolType(DiskDef disk, DiskTO volume, boolean isWindowsTemplate,
boolean isUefiEnabled, boolean isSecureBoot, KVMPhysicalDisk physicalDisk, int devId,
DiskDef.DiskBus diskBusType, DiskDef.DiskBus diskBusTypeData, Map<String, String> details) {
boolean skipForceDiskController = MapUtils.getBoolean(details, VmDetailConstants.KVM_SKIP_FORCE_DISK_CONTROLLER,
false);
if (skipForceDiskController) {
disk.defFileBasedDisk(physicalDisk.getPath(), devId, Volume.Type.DATADISK.equals(volume.getType()) ?
diskBusTypeData : diskBusType, DiskDef.DiskFmtType.QCOW2);
return;
}
if (volume.getType() == Volume.Type.DATADISK && !(isWindowsTemplate && isUefiEnabled)) {
disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusTypeData, DiskDef.DiskFmtType.QCOW2);
} else {
if (isSecureBoot) {
disk.defFileBasedDisk(physicalDisk.getPath(), devId, DiskDef.DiskFmtType.QCOW2, isWindowsTemplate);
} else {
disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusType, DiskDef.DiskFmtType.QCOW2);
}
}
}
public void createVbd(final Connect conn, final VirtualMachineTO vmSpec, final String vmName, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException, URISyntaxException { public void createVbd(final Connect conn, final VirtualMachineTO vmSpec, final String vmName, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException, URISyntaxException {
final Map<String, String> details = vmSpec.getDetails(); final Map<String, String> details = vmSpec.getDetails();
final List<DiskTO> disks = Arrays.asList(vmSpec.getDisks()); final List<DiskTO> disks = Arrays.asList(vmSpec.getDisks());
@ -3654,15 +3692,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
disk.setDiscard(DiscardType.UNMAP); disk.setDiscard(DiscardType.UNMAP);
} }
} else { } else {
if (volume.getType() == Volume.Type.DATADISK && !(isWindowsTemplate && isUefiEnabled)) { defineDiskForDefaultPoolType(disk, volume, isWindowsTemplate, isUefiEnabled, isSecureBoot,
disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusTypeData, DiskDef.DiskFmtType.QCOW2); physicalDisk, devId, diskBusType, diskBusTypeData, details);
} else {
if (isSecureBoot) {
disk.defFileBasedDisk(physicalDisk.getPath(), devId, DiskDef.DiskFmtType.QCOW2, isWindowsTemplate);
} else {
disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusType, DiskDef.DiskFmtType.QCOW2);
}
}
} }
pool.customizeLibvirtDiskDef(disk); pool.customizeLibvirtDiskDef(disk);
} }

View File

@ -61,12 +61,9 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import com.cloud.cpu.CPU;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.VmDetailConstants;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
import org.apache.cloudstack.storage.command.AttachAnswer; import org.apache.cloudstack.storage.command.AttachAnswer;
import org.apache.cloudstack.storage.command.AttachCommand; import org.apache.cloudstack.storage.command.AttachCommand;
@ -186,6 +183,7 @@ import com.cloud.agent.api.to.VolumeTO;
import com.cloud.agent.properties.AgentProperties; import com.cloud.agent.properties.AgentProperties;
import com.cloud.agent.properties.AgentPropertiesFileHandler; import com.cloud.agent.properties.AgentPropertiesFileHandler;
import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
import com.cloud.cpu.CPU;
import com.cloud.exception.InternalErrorException; import com.cloud.exception.InternalErrorException;
import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool; import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
@ -228,13 +226,15 @@ import com.cloud.storage.template.TemplateLocation;
import com.cloud.template.VirtualMachineTemplate.BootloaderType; import com.cloud.template.VirtualMachineTemplate.BootloaderType;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script; import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.OutputInterpreter.OneLineParser; import com.cloud.utils.script.OutputInterpreter.OneLineParser;
import com.cloud.utils.script.Script;
import com.cloud.utils.ssh.SshHelper; import com.cloud.utils.ssh.SshHelper;
import com.cloud.vm.DiskProfile; import com.cloud.vm.DiskProfile;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VirtualMachine.PowerState;
import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachine.Type;
import com.cloud.vm.VmDetailConstants;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class LibvirtComputingResourceTest { public class LibvirtComputingResourceTest {
@ -251,6 +251,19 @@ public class LibvirtComputingResourceTest {
Connect connMock; Connect connMock;
@Mock @Mock
LibvirtDomainXMLParser parserMock; LibvirtDomainXMLParser parserMock;
@Mock
private DiskDef diskDef;
@Mock
private DiskTO volume;
@Mock
private KVMPhysicalDisk physicalDisk;
@Mock
private Map<String, String> details;
private static final String PHYSICAL_DISK_PATH = "/path/to/disk";
private static final int DEV_ID = 1;
private static final DiskDef.DiskBus DISK_BUS_TYPE = DiskDef.DiskBus.VIRTIO;
private static final DiskDef.DiskBus DISK_BUS_TYPE_DATA = DiskDef.DiskBus.SCSI;
@Mock @Mock
DiskTO diskToMock; DiskTO diskToMock;
@ -7145,4 +7158,49 @@ public class LibvirtComputingResourceTest {
Assert.assertEquals("-mmx", cpuFeatures.get(2)); Assert.assertEquals("-mmx", cpuFeatures.get(2));
Assert.assertEquals("hle", cpuFeatures.get(3)); Assert.assertEquals("hle", cpuFeatures.get(3));
} }
@Test
public void defineDiskForDefaultPoolTypeSkipsForceDiskController() {
Map<String, String> details = new HashMap<>();
details.put(VmDetailConstants.KVM_SKIP_FORCE_DISK_CONTROLLER, "true");
Mockito.when(volume.getType()).thenReturn(Volume.Type.DATADISK);
Mockito.when(physicalDisk.getPath()).thenReturn(PHYSICAL_DISK_PATH);
libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, details);
Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE_DATA, DiskDef.DiskFmtType.QCOW2);
}
@Test
public void defineDiskForDefaultPoolTypeUsesDiskBusTypeDataForDataDiskWithoutWindowsAndUefi() {
Map<String, String> details = new HashMap<>();
Mockito.when(volume.getType()).thenReturn(Volume.Type.DATADISK);
Mockito.when(physicalDisk.getPath()).thenReturn(PHYSICAL_DISK_PATH);
libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, details);
Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE_DATA, DiskDef.DiskFmtType.QCOW2);
}
@Test
public void defineDiskForDefaultPoolTypeUsesDiskBusTypeForRootDisk() {
Map<String, String> details = new HashMap<>();
Mockito.when(volume.getType()).thenReturn(Volume.Type.ROOT);
Mockito.when(physicalDisk.getPath()).thenReturn(PHYSICAL_DISK_PATH);
libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, details);
Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE, DiskDef.DiskFmtType.QCOW2);
}
@Test
public void defineDiskForDefaultPoolTypeUsesSecureBootConfiguration() {
Map<String, String> details = new HashMap<>();
Mockito.when(volume.getType()).thenReturn(Volume.Type.ROOT);
Mockito.when(physicalDisk.getPath()).thenReturn(PHYSICAL_DISK_PATH);
libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, true, true, true, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, details);
Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DiskDef.DiskFmtType.QCOW2, true);
}
@Test
public void defineDiskForDefaultPoolTypeHandlesNullDetails() {
Mockito.when(volume.getType()).thenReturn(Volume.Type.DATADISK);
Mockito.when(physicalDisk.getPath()).thenReturn(PHYSICAL_DISK_PATH);
libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, null);
Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE_DATA, DiskDef.DiskFmtType.QCOW2);
}
} }

View File

@ -5390,6 +5390,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
options.put(VmDetailConstants.VIRTUAL_TPM_VERSION, Arrays.asList("1.2", "2.0")); options.put(VmDetailConstants.VIRTUAL_TPM_VERSION, Arrays.asList("1.2", "2.0"));
options.put(VmDetailConstants.GUEST_CPU_MODE, Arrays.asList("custom", "host-model", "host-passthrough")); options.put(VmDetailConstants.GUEST_CPU_MODE, Arrays.asList("custom", "host-model", "host-passthrough"));
options.put(VmDetailConstants.GUEST_CPU_MODEL, Collections.emptyList()); options.put(VmDetailConstants.GUEST_CPU_MODEL, Collections.emptyList());
options.put(VmDetailConstants.KVM_SKIP_FORCE_DISK_CONTROLLER, Arrays.asList("true", "false"));
} }
if (HypervisorType.VMware.equals(hypervisorType)) { if (HypervisorType.VMware.equals(hypervisorType)) {

View File

@ -1044,6 +1044,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
category = config.getCategory(); category = config.getCategory();
} }
if (value == null) {
throw new InvalidParameterValueException(String.format("The new value for the [%s] configuration must be given.", name));
}
validateIpAddressRelatedConfigValues(name, value); validateIpAddressRelatedConfigValues(name, value);
validateConflictingConfigValue(name, value); validateConflictingConfigValue(name, value);
@ -1052,10 +1056,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
throw new CloudRuntimeException("Only Root Admin is allowed to edit this configuration."); throw new CloudRuntimeException("Only Root Admin is allowed to edit this configuration.");
} }
if (value == null) {
return _configDao.findByName(name);
}
ConfigKey.Scope scope = null; ConfigKey.Scope scope = null;
Long id = null; Long id = null;
int paramCountCheck = 0; int paramCountCheck = 0;

View File

@ -2459,7 +2459,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
} }
} }
if (volume != null && ImageFormat.QCOW2.equals(volume.getFormat()) && !Volume.State.Allocated.equals(volume.getState()) && !StoragePoolType.StorPool.equals(volume.getPoolType())) { if (volume != null && ImageFormat.QCOW2.equals(volume.getFormat()) && !Volume.State.Allocated.equals(volume.getState()) &&
!Arrays.asList(StoragePoolType.StorPool, StoragePoolType.Linstor).contains(volume.getPoolType())) {
String message = "Unable to shrink volumes of type QCOW2"; String message = "Unable to shrink volumes of type QCOW2";
logger.warn(message); logger.warn(message);
throw new InvalidParameterValueException(message); throw new InvalidParameterValueException(message);