mirror of https://github.com/apache/cloudstack.git
kvm: implement copyPhysicalDisk on MultipathNVMeOFAdapterBase
The NVMe-oF KVM adapter refused every template copy request from the adaptive storage orchestrator with UnsupportedOperationException, which made it impossible to use an NVMe-TCP pool as primary storage for a VM root disk: every deploy that landed a root volume on the pool failed as soon as CloudStack tried to lay down the template. Implement it the same way FiberChannel (SCSI) does: the storage provider creates and connects a raw namespace ahead of time, then the adapter resolves the host-side /dev/disk/by-id/nvme-eui.<NGUID> path via the existing getPhysicalDisk plumbing (which will nvme ns-rescan and wait for the symlink if the kernel has not yet picked it up) and qemu-img converts the source image into the raw block device. User-space encrypted source or destination volumes are rejected: the FlashArray already encrypts at rest and layering qemu-img LUKS on top of a hostgroup-scoped namespace shared between hosts is not a sensible layering. Source encryption would also break on migration because the passphrase does not travel. With this change a CloudStack KVM VM can have its ROOT volume on an NVMe-TCP pool (tested end-to-end on 4.23-SNAPSHOT against Purity 6.7.7: template copy, first boot, live migrate with data disk, VM snapshot with quiesce, and revert all work). Signed-off-by: Eugenio Grosso <eugenio.grosso@gmail.com>
This commit is contained in:
parent
ff03d9f4f3
commit
c0cdfa41da
|
|
@ -25,6 +25,9 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgException;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
import org.libvirt.LibvirtException;
|
||||
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
|
@ -280,12 +283,61 @@ public abstract class MultipathNVMeOFAdapterBase implements StorageAdaptor {
|
|||
|
||||
@Override
|
||||
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'copyPhysicalDisk'");
|
||||
return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a template or source disk into a pre-provisioned NVMe namespace on
|
||||
* this pool, so it can be consumed by a VM as a root or data volume.
|
||||
*
|
||||
* The destination namespace is expected to have already been created on
|
||||
* the storage provider and connected to this host's hostgroup (that is
|
||||
* the storage orchestrator's responsibility, not the KVM adapter's). All
|
||||
* this method does is resolve the destination device path via
|
||||
* {@link #getPhysicalDisk} - which will nvme ns-rescan and wait for the
|
||||
* by-id/nvme-eui.<NGUID> symlink to show up if the kernel has not
|
||||
* picked it up yet - and {@code qemu-img convert} the source image into
|
||||
* the raw block device.
|
||||
*
|
||||
* User-space encryption passphrases are not supported: the provider
|
||||
* already encrypts at rest and qemu-img LUKS on top of a shared
|
||||
* hostgroup-scoped namespace is not a sensible layering.
|
||||
*/
|
||||
@Override
|
||||
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'copyPhysicalDisk'");
|
||||
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout,
|
||||
byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType) {
|
||||
if (disk == null || StringUtils.isEmpty(name) || destPool == null) {
|
||||
throw new CloudRuntimeException("Unable to copy disk to NVMe-oF pool: source disk, destination volume name or destination pool not specified");
|
||||
}
|
||||
if (srcPassphrase != null || destPassphrase != null) {
|
||||
throw new CloudRuntimeException("NVMe-oF adapter does not support user-space encrypted source or destination volumes");
|
||||
}
|
||||
|
||||
KVMPhysicalDisk destDisk = destPool.getPhysicalDisk(name);
|
||||
if (destDisk == null || StringUtils.isEmpty(destDisk.getPath())) {
|
||||
throw new CloudRuntimeException("Unable to resolve NVMe namespace for destination volume [" + name + "] on pool [" + destPool.getUuid() + "]");
|
||||
}
|
||||
|
||||
destDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
|
||||
destDisk.setVirtualSize(disk.getVirtualSize());
|
||||
destDisk.setSize(disk.getSize());
|
||||
|
||||
LOGGER.info(String.format("Copying source disk [path=%s, format=%s, virtualSize=%d] to NVMe-oF namespace [path=%s] on pool [%s]",
|
||||
disk.getPath(), disk.getFormat(), disk.getVirtualSize(), destDisk.getPath(), destPool.getUuid()));
|
||||
|
||||
QemuImgFile srcFile = new QemuImgFile(disk.getPath(), disk.getFormat());
|
||||
QemuImgFile destFile = new QemuImgFile(destDisk.getPath(), destDisk.getFormat());
|
||||
|
||||
try {
|
||||
QemuImg qemu = new QemuImg(timeout);
|
||||
qemu.convert(srcFile, destFile, true);
|
||||
} catch (QemuImgException | LibvirtException e) {
|
||||
throw new CloudRuntimeException("Failed to copy source disk [" + disk.getPath() + "] to NVMe-oF namespace ["
|
||||
+ destDisk.getPath() + "] on pool [" + destPool.getUuid() + "]: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
LOGGER.info("Successfully copied source disk to NVMe-oF namespace [" + destDisk.getPath() + "] on pool [" + destPool.getUuid() + "]");
|
||||
return destDisk;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Reference in New Issue