Skip to content

Commit

Permalink
Merge branch 'main' into ui-improv
Browse files Browse the repository at this point in the history
  • Loading branch information
borisstoyanov authored Nov 13, 2024
2 parents 320b7f0 + f7b7339 commit e289502
Show file tree
Hide file tree
Showing 16 changed files with 276 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2078,8 +2078,8 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath, backingPath);
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
migrateDiskInfoList.add(migrateDiskInfo);
prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath());
}
prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath());

migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);

Expand Down Expand Up @@ -2479,7 +2479,8 @@ protected void verifyLiveMigrationForKVM(Map<VolumeInfo, DataStore> volumeDataSt
throw new CloudRuntimeException("Destination storage pool with ID " + dataStore.getId() + " was not located.");
}

if (srcStoragePoolVO.isManaged() && srcStoragePoolVO.getId() != destStoragePoolVO.getId()) {
boolean isSrcAndDestPoolPowerFlexStorage = srcStoragePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex) && destStoragePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex);
if (srcStoragePoolVO.isManaged() && !isSrcAndDestPoolPowerFlexStorage && srcStoragePoolVO.getId() != destStoragePoolVO.getId()) {
throw new CloudRuntimeException("Migrating a volume online with KVM from managed storage is not currently supported.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
protected static final String DEFAULT_TUNGSTEN_VIF_DRIVER_CLASS_NAME = "com.cloud.hypervisor.kvm.resource.VRouterVifDriver";
private final static long HYPERVISOR_LIBVIRT_VERSION_SUPPORTS_IO_URING = 6003000;
private final static long HYPERVISOR_QEMU_VERSION_SUPPORTS_IO_URING = 5000000;
private final static long HYPERVISOR_QEMU_VERSION_IDE_DISCARD_FIXED = 7000000;

protected HypervisorType hypervisorType;
protected String hypervisorURI;
Expand Down Expand Up @@ -3050,7 +3051,7 @@ public int compare(final DiskTO arg0, final DiskTO arg1) {
}
} else if (volume.getType() != Volume.Type.ISO) {
final PrimaryDataStoreTO store = (PrimaryDataStoreTO)data.getDataStore();
physicalDisk = storagePoolManager.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
physicalDisk = getStoragePoolMgr().getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
pool = physicalDisk.getPool();
}

Expand Down Expand Up @@ -3152,7 +3153,7 @@ public int compare(final DiskTO arg0, final DiskTO arg1) {
else {
disk.defBlockBasedDisk(physicalDisk.getPath(), devId, diskBusType);
}
if (pool.getType() == StoragePoolType.Linstor) {
if (pool.getType() == StoragePoolType.Linstor && isQemuDiscardBugFree(diskBusType)) {
disk.setDiscard(DiscardType.UNMAP);
}
} else {
Expand Down Expand Up @@ -3299,6 +3300,16 @@ private boolean isIoUringEnabled() {
return isUbuntuHost() || isIoUringSupportedByQemu();
}

/**
* Qemu has a bug with discard enabled on IDE bus devices if qemu version < 7.0.
* <a href="https://bugzilla.redhat.com/show_bug.cgi?id=2029980">redhat bug entry</a>
* @param diskBus used for the disk
* @return true if it is safe to enable discard, otherwise false.
*/
public boolean isQemuDiscardBugFree(DiskDef.DiskBus diskBus) {
return diskBus != DiskDef.DiskBus.IDE || getHypervisorQemuVersion() >= HYPERVISOR_QEMU_VERSION_IDE_DISCARD_FIXED;
}

public boolean isUbuntuHost() {
Map<String, String> versionString = getVersionStrings();
String hostKey = "Host.OS";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public Ternary<String, String, String> getInterfaceDetails(String interfaceName)

@Override
public Answer execute(final OvsFetchInterfaceCommand command, final LibvirtComputingResource libvirtComputingResource) {
final String label = "'" + command.getLabel() + "'";
final String label = command.getLabel();

logger.debug("Will look for network with name-label:" + label);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1447,7 +1447,7 @@ protected synchronized void attachOrDetachDisk(final Connect conn, final boolean
diskdef.defFileBasedDisk(attachingDisk.getPath(), devId, busT, DiskDef.DiskFmtType.QCOW2);
} else if (attachingDisk.getFormat() == PhysicalDiskFormat.RAW) {
diskdef.defBlockBasedDisk(attachingDisk.getPath(), devId, busT);
if (attachingPool.getType() == StoragePoolType.Linstor) {
if (attachingPool.getType() == StoragePoolType.Linstor && resource.isQemuDiscardBugFree(busT)) {
diskdef.setDiscard(DiscardType.UNMAP);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@

import com.cloud.utils.net.NetUtils;

import com.cloud.vm.VmDetailConstants;
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
import org.apache.cloudstack.storage.command.AttachAnswer;
import org.apache.cloudstack.storage.command.AttachCommand;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
import org.apache.cloudstack.utils.linux.CPUStat;
import org.apache.cloudstack.utils.linux.MemStat;
Expand Down Expand Up @@ -6415,4 +6418,114 @@ public void calculateVmMetricsTestOldStatsIsNotNullCalculatesUtilization() throw
Assert.assertEquals(newStats.getDiskReadKBs() - oldStats.getDiskReadKBs(), metrics.getDiskReadKBs(), 0);
Assert.assertEquals(newStats.getDiskWriteKBs() - oldStats.getDiskWriteKBs(), metrics.getDiskWriteKBs(), 0);
}

@Test
public void createLinstorVdb() throws LibvirtException, InternalErrorException, URISyntaxException {
final Connect connect = Mockito.mock(Connect.class);

final int id = random.nextInt(65534);
final String name = "test-instance-1";

final int cpus = 2;
final int speed = 1024;
final int minRam = 256 * 1024;
final int maxRam = 512 * 1024;
final String os = "Ubuntu";
final String vncPassword = "mySuperSecretPassword";

final VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, speed, minRam,
maxRam, BootloaderType.HVM, os, false, false, vncPassword);
to.setVncAddr("");
to.setArch("x86_64");
to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9");
to.setVcpuMaxLimit(cpus + 1);
final HashMap<String, String> vmToDetails = new HashMap<>();
to.setDetails(vmToDetails);

String diskLinPath = "9ebe53c1-3d35-46e5-b7aa-6fc223ba0fcf";
final DiskTO diskTO = new DiskTO();
diskTO.setDiskSeq(1L);
diskTO.setType(Volume.Type.ROOT);
diskTO.setDetails(new HashMap<>());
diskTO.setPath(diskLinPath);

final PrimaryDataStoreTO primaryDataStoreTO = Mockito.mock(PrimaryDataStoreTO.class);
String pDSTOUUID = "9ebe53c1-3d35-46e5-b7aa-6fc223ac4fcf";
when(primaryDataStoreTO.getPoolType()).thenReturn(StoragePoolType.Linstor);
when(primaryDataStoreTO.getUuid()).thenReturn(pDSTOUUID);

VolumeObjectTO dataTO = new VolumeObjectTO();

dataTO.setUuid("12be53c1-3d35-46e5-b7aa-6fc223ba0f34");
dataTO.setPath(diskTO.getPath());
dataTO.setDataStore(primaryDataStoreTO);
diskTO.setData(dataTO);
to.setDisks(new DiskTO[]{diskTO});

String path = "/dev/drbd1020";
final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class);
final KVMPhysicalDisk vol = Mockito.mock(KVMPhysicalDisk.class);

when(libvirtComputingResourceSpy.getStoragePoolMgr()).thenReturn(storagePoolMgr);
when(storagePool.getType()).thenReturn(StoragePoolType.Linstor);
when(storagePoolMgr.getPhysicalDisk(StoragePoolType.Linstor, pDSTOUUID, diskLinPath)).thenReturn(vol);
when(vol.getPath()).thenReturn(path);
when(vol.getPool()).thenReturn(storagePool);
when(vol.getFormat()).thenReturn(PhysicalDiskFormat.RAW);

// 1. test Bus: IDE and broken qemu version -> NO discard
when(libvirtComputingResourceSpy.getHypervisorQemuVersion()).thenReturn(6000000L);
vmToDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, DiskDef.DiskBus.IDE.name());
{
LibvirtVMDef vm = new LibvirtVMDef();
vm.addComp(new DevicesDef());
libvirtComputingResourceSpy.createVbd(connect, to, name, vm);

DiskDef rootDisk = vm.getDevices().getDisks().get(0);
assertEquals(DiskDef.DiskType.BLOCK, rootDisk.getDiskType());
assertEquals(DiskDef.DiskBus.IDE, rootDisk.getBusType());
assertEquals(DiskDef.DiscardType.IGNORE, rootDisk.getDiscard());
}

// 2. test Bus: VIRTIO and broken qemu version -> discard unmap
vmToDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, DiskDef.DiskBus.VIRTIO.name());
{
LibvirtVMDef vm = new LibvirtVMDef();
vm.addComp(new DevicesDef());
libvirtComputingResourceSpy.createVbd(connect, to, name, vm);

DiskDef rootDisk = vm.getDevices().getDisks().get(0);
assertEquals(DiskDef.DiskType.BLOCK, rootDisk.getDiskType());
assertEquals(DiskDef.DiskBus.VIRTIO, rootDisk.getBusType());
assertEquals(DiskDef.DiscardType.UNMAP, rootDisk.getDiscard());
}

// 3. test Bus; IDE and "good" qemu version -> discard unmap
vmToDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, DiskDef.DiskBus.IDE.name());
when(libvirtComputingResourceSpy.getHypervisorQemuVersion()).thenReturn(7000000L);
{
LibvirtVMDef vm = new LibvirtVMDef();
vm.addComp(new DevicesDef());
libvirtComputingResourceSpy.createVbd(connect, to, name, vm);

DiskDef rootDisk = vm.getDevices().getDisks().get(0);
assertEquals(DiskDef.DiskType.BLOCK, rootDisk.getDiskType());
assertEquals(DiskDef.DiskBus.IDE, rootDisk.getBusType());
assertEquals(DiskDef.DiscardType.UNMAP, rootDisk.getDiscard());
}

// 4. test Bus: VIRTIO and "good" qemu version -> discard unmap
vmToDetails.put(VmDetailConstants.ROOT_DISK_CONTROLLER, DiskDef.DiskBus.VIRTIO.name());
{
LibvirtVMDef vm = new LibvirtVMDef();
vm.addComp(new DevicesDef());
libvirtComputingResourceSpy.createVbd(connect, to, name, vm);

DiskDef rootDisk = vm.getDevices().getDisks().get(0);
assertEquals(DiskDef.DiskType.BLOCK, rootDisk.getDiskType());
assertEquals(DiskDef.DiskBus.VIRTIO, rootDisk.getBusType());
assertEquals(DiskDef.DiscardType.UNMAP, rootDisk.getDiscard());
}
}
}
7 changes: 7 additions & 0 deletions plugins/storage/volume/linstor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2024-10-28]

### Fixed

- Disable discard="unmap" for ide devices and qemu < 7.0
https://bugzilla.redhat.com/show_bug.cgi?id=2029980

## [2024-10-04]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.cloud.hypervisor.kvm.storage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -48,6 +49,7 @@
import com.linbit.linstor.api.model.Resource;
import com.linbit.linstor.api.model.ResourceConnectionModify;
import com.linbit.linstor.api.model.ResourceDefinition;
import com.linbit.linstor.api.model.ResourceDefinitionModify;
import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.ResourceWithVolumes;
Expand Down Expand Up @@ -235,6 +237,34 @@ public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, Qemu
}
}

private void setAllowTwoPrimariesOnRD(DevelopersApi api, String rscName) throws ApiException {
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
Properties props = new Properties();
props.put("DrbdOptions/Net/allow-two-primaries", "yes");
props.put("DrbdOptions/Net/protocol", "C");
rdm.setOverrideProps(props);
ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
if (answers.hasError()) {
logger.error(String.format("Unable to set protocol C and 'allow-two-primaries' on %s", rscName));
// do not fail here as adding allow-two-primaries property is only a problem while live migrating
}
}

private void setAllowTwoPrimariesOnRc(DevelopersApi api, String rscName, String inUseNode) throws ApiException {
ResourceConnectionModify rcm = new ResourceConnectionModify();
Properties props = new Properties();
props.put("DrbdOptions/Net/allow-two-primaries", "yes");
props.put("DrbdOptions/Net/protocol", "C");
rcm.setOverrideProps(props);
ApiCallRcList answers = api.resourceConnectionModify(rscName, inUseNode, localNodeName, rcm);
if (answers.hasError()) {
logger.error(String.format(
"Unable to set protocol C and 'allow-two-primaries' on %s/%s/%s",
inUseNode, localNodeName, rscName));
// do not fail here as adding allow-two-primaries property is only a problem while live migrating
}
}

/**
* Checks if the given resource is in use by drbd on any host and
* if so set the drbd option allow-two-primaries
Expand All @@ -246,16 +276,13 @@ private void allow2PrimariesIfInUse(DevelopersApi api, String rscName) throws Ap
String inUseNode = LinstorUtil.isResourceInUse(api, rscName);
if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) {
// allow 2 primaries for live migration, should be removed by disconnect on the other end
ResourceConnectionModify rcm = new ResourceConnectionModify();
Properties props = new Properties();
props.put("DrbdOptions/Net/allow-two-primaries", "yes");
props.put("DrbdOptions/Net/protocol", "C");
rcm.setOverrideProps(props);
ApiCallRcList answers = api.resourceConnectionModify(rscName, inUseNode, localNodeName, rcm);
if (answers.hasError()) {
logger.error("Unable to set protocol C and 'allow-two-primaries' on {}/{}/{}",
inUseNode, localNodeName, rscName);
// do not fail here as adding allow-two-primaries property is only a problem while live migrating

// if non hyperconverged setup, we have to set allow-two-primaries on the resource-definition
// as there is no resource connection between diskless nodes.
if (LinstorUtil.areResourcesDiskless(api, rscName, Arrays.asList(inUseNode, localNodeName))) {
setAllowTwoPrimariesOnRD(api, rscName);
} else {
setAllowTwoPrimariesOnRc(api, rscName, inUseNode);
}
}
}
Expand Down Expand Up @@ -294,11 +321,22 @@ public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<S
return true;
}

private void removeTwoPrimariesRcProps(DevelopersApi api, String inUseNode, String rscName) throws ApiException {
private void removeTwoPrimariesRDProps(DevelopersApi api, String rscName, List<String> deleteProps)
throws ApiException {
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
rdm.deleteProps(deleteProps);
ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
if (answers.hasError()) {
logger.error(
String.format("Failed to remove 'protocol' and 'allow-two-primaries' on %s: %s",
rscName, LinstorUtil.getBestErrorMessage(answers)));
// do not fail here as removing allow-two-primaries property isn't fatal
}
}

private void removeTwoPrimariesRcProps(DevelopersApi api, String rscName, String inUseNode, List<String> deleteProps)
throws ApiException {
ResourceConnectionModify rcm = new ResourceConnectionModify();
List<String> deleteProps = new ArrayList<>();
deleteProps.add("DrbdOptions/Net/allow-two-primaries");
deleteProps.add("DrbdOptions/Net/protocol");
rcm.deleteProps(deleteProps);
ApiCallRcList answers = api.resourceConnectionModify(rscName, localNodeName, inUseNode, rcm);
if (answers.hasError()) {
Expand All @@ -310,6 +348,15 @@ private void removeTwoPrimariesRcProps(DevelopersApi api, String inUseNode, Stri
}
}

private void removeTwoPrimariesProps(DevelopersApi api, String inUseNode, String rscName) throws ApiException {
List<String> deleteProps = new ArrayList<>();
deleteProps.add("DrbdOptions/Net/allow-two-primaries");
deleteProps.add("DrbdOptions/Net/protocol");

removeTwoPrimariesRDProps(api, rscName, deleteProps);
removeTwoPrimariesRcProps(api, rscName, inUseNode, deleteProps);
}

private boolean tryDisconnectLinstor(String volumePath, KVMStoragePool pool)
{
if (volumePath == null) {
Expand Down Expand Up @@ -343,7 +390,7 @@ private boolean tryDisconnectLinstor(String volumePath, KVMStoragePool pool)
try {
String inUseNode = LinstorUtil.isResourceInUse(api, rsc.getName());
if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) {
removeTwoPrimariesRcProps(api, inUseNode, rsc.getName());
removeTwoPrimariesProps(api, inUseNode, rsc.getName());
}
} catch (ApiException apiEx) {
logger.error(apiEx.getBestMessage());
Expand Down
Loading

0 comments on commit e289502

Please sign in to comment.