diff --git a/api/src/main/java/com/cloud/agent/api/to/StorageFilerTO.java b/api/src/main/java/com/cloud/agent/api/to/StorageFilerTO.java index 8f58c9e1c91..e361e7a141f 100644 --- a/api/src/main/java/com/cloud/agent/api/to/StorageFilerTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/StorageFilerTO.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.agent.api.to; +import com.cloud.agent.api.LogLevel; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StoragePool; @@ -24,6 +25,7 @@ public class StorageFilerTO { String uuid; String host; String path; + @LogLevel(LogLevel.Log4jLevel.Off) String userInfo; int port; StoragePoolType type; diff --git a/api/src/main/java/com/cloud/storage/MigrationOptions.java b/api/src/main/java/com/cloud/storage/MigrationOptions.java index 38c1ee87bbe..a39a2a7c827 100644 --- a/api/src/main/java/com/cloud/storage/MigrationOptions.java +++ b/api/src/main/java/com/cloud/storage/MigrationOptions.java @@ -25,6 +25,7 @@ public class MigrationOptions implements Serializable { private String srcPoolUuid; private Storage.StoragePoolType srcPoolType; private Type type; + private ScopeType scopeType; private String srcBackingFilePath; private boolean copySrcTemplate; private String srcVolumeUuid; @@ -37,18 +38,20 @@ public class MigrationOptions implements Serializable { public MigrationOptions() { } - public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcBackingFilePath, boolean copySrcTemplate) { + public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcBackingFilePath, boolean copySrcTemplate, ScopeType scopeType) { this.srcPoolUuid = srcPoolUuid; this.srcPoolType = srcPoolType; this.type = Type.LinkedClone; + this.scopeType = scopeType; this.srcBackingFilePath = srcBackingFilePath; this.copySrcTemplate = copySrcTemplate; } - public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcVolumeUuid) { + public MigrationOptions(String srcPoolUuid, Storage.StoragePoolType srcPoolType, String srcVolumeUuid, ScopeType scopeType) { this.srcPoolUuid = srcPoolUuid; this.srcPoolType = srcPoolType; this.type = Type.FullClone; + this.scopeType = scopeType; this.srcVolumeUuid = srcVolumeUuid; } @@ -60,6 +63,8 @@ public class MigrationOptions implements Serializable { return srcPoolType; } + public ScopeType getScopeType() { return scopeType; } + public String getSrcBackingFilePath() { return srcBackingFilePath; } diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index 0a4858cae73..c42935e6291 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -1738,20 +1738,16 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { */ protected MigrationOptions createLinkedCloneMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, String srcVolumeBackingFile, String srcPoolUuid, Storage.StoragePoolType srcPoolType) { VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(destVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId(), null); - - // if template exists on destination, use it as the backing file - if (ref != null) { - return new MigrationOptions(destVolumeInfo.getDataStore().getUuid(), destVolumeInfo.getStoragePoolType(), ref.getInstallPath(), false); - } else { - return new MigrationOptions(srcPoolUuid, srcPoolType, srcVolumeBackingFile, true); - } + boolean updateBackingFileReference = ref == null; + String backingFile = !updateBackingFileReference ? ref.getInstallPath() : srcVolumeBackingFile; + return new MigrationOptions(srcPoolUuid, srcPoolType, backingFile, updateBackingFileReference, srcVolumeInfo.getDataStore().getScope().getScopeType()); } /** * Return expected MigrationOptions for a full clone volume live storage migration */ protected MigrationOptions createFullCloneMigrationOptions(VolumeInfo srcVolumeInfo, VirtualMachineTO vmTO, Host srcHost, String srcPoolUuid, Storage.StoragePoolType srcPoolType) { - return new MigrationOptions(srcPoolUuid, srcPoolType, srcVolumeInfo.getPath()); + return new MigrationOptions(srcPoolUuid, srcPoolType, srcVolumeInfo.getPath(), srcVolumeInfo.getDataStore().getScope().getScopeType()); } /** diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index cbda6baf68f..46fed5252cf 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -24,7 +24,6 @@ import java.io.StringReader; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -55,7 +54,6 @@ import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.utils.cryptsetup.CryptSetup; -import org.apache.cloudstack.utils.cryptsetup.KeyFile; import org.apache.cloudstack.utils.hypervisor.HypervisorUtils; import org.apache.cloudstack.utils.linux.CPUStat; import org.apache.cloudstack.utils.linux.KVMHostInfo; @@ -2901,6 +2899,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv pool.getUuid(), devId, diskBusType, DiskProtocol.RBD, DiskDef.DiskFmtType.RAW); } else if (pool.getType() == StoragePoolType.PowerFlex) { disk.defBlockBasedDisk(physicalDisk.getPath(), devId, diskBusTypeData); + if (physicalDisk.getFormat().equals(PhysicalDiskFormat.QCOW2)) { + disk.setDiskFormatType(DiskDef.DiskFmtType.QCOW2); + } } else if (pool.getType() == StoragePoolType.Gluster) { final String mountpoint = pool.getLocalPath(); final String path = physicalDisk.getPath(); @@ -4661,28 +4662,21 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv Path testFile = Paths.get(javaTempDir, UUID.randomUUID().toString()).normalize().toAbsolutePath(); String objectName = "sec0"; + Map options = new HashMap(); List passphraseObjects = new ArrayList<>(); QemuImgFile file = new QemuImgFile(testFile.toString(), 64<<20, PhysicalDiskFormat.QCOW2); - - try (KeyFile keyFile = new KeyFile(UUID.randomUUID().toString().getBytes())){ + try { QemuImg qemu = new QemuImg(0); - passphraseObjects.add(QemuObject.prepareSecretForQemuImg(PhysicalDiskFormat.QCOW2, QemuObject.EncryptFormat.LUKS, keyFile.toString(), "sec0", options)); - qemu.create(file, null, options, passphraseObjects); - s_logger.info("Host's qemu install supports encryption"); - } catch (QemuImgException | IOException | LibvirtException ex) { + if (!qemu.supportsImageFormat(PhysicalDiskFormat.LUKS)) { + return false; + } + } catch (QemuImgException | LibvirtException ex) { s_logger.info("Host's qemu install doesn't support encryption", ex); return false; } - // cleanup - try { - Files.deleteIfExists(testFile); - } catch (IOException ex) { - s_logger.warn(String.format("Failed to clean up test file '%s'", testFile.toAbsolutePath()), ex); - } - // test cryptsetup CryptSetup crypt = new CryptSetup(); if (!crypt.isSupported()) { diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index 922e2bff7dd..7ad03ec7e21 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -977,6 +977,8 @@ public class LibvirtVMDef { return _diskFmtType; } + public void setDiskFormatType(DiskFmtType type) { _diskFmtType = type; } + public void setBytesReadRate(Long bytesReadRate) { _bytesReadRate = bytesReadRate; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java index d1f0e1489b1..f08d05976c4 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java @@ -24,12 +24,15 @@ import static com.cloud.utils.NumbersUtil.toHumanReadableSize; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import com.cloud.hypervisor.kvm.storage.ScaleIOStorageAdaptor; import org.apache.cloudstack.utils.cryptsetup.KeyFile; import org.apache.cloudstack.utils.qemu.QemuImageOptions; 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.apache.cloudstack.utils.qemu.QemuObject; import org.apache.log4j.Logger; import org.libvirt.Connect; @@ -63,7 +66,7 @@ public final class LibvirtResizeVolumeCommandWrapper extends CommandWrapper info = qemu.info(qemuFile); + if (info.containsKey(QemuImg.VIRTUAL_SIZE)) { + newSize = Long.parseLong(info.get(QemuImg.VIRTUAL_SIZE)); + } else { + throw new CloudRuntimeException("Unable to determine size of powerflex RAW volume for resize notification"); + } + } catch (QemuImgException | LibvirtException ex) { + throw new CloudRuntimeException("Error when inspecting powerflex volume for size", ex); + } + } + + if (pool.getType() != StoragePoolType.RBD && pool.getType() != StoragePoolType.Linstor && pool.getType() != StoragePoolType.PowerFlex) { type = libvirtComputingResource.getResizeScriptType(pool, vol); if (type.equals("QCOW2") && shrinkOk) { return new ResizeVolumeAnswer(command, false, "Unable to shrink volumes of type " + type); @@ -96,8 +122,8 @@ public final class LibvirtResizeVolumeCommandWrapper extends CommandWrapper" + " {0}" + " " + " {1}" + " " + " "); @@ -2186,4 +2185,23 @@ public class KVMStorageProcessor implements StorageProcessor { s_logger.info("SyncVolumePathCommand not currently applicable for KVMStorageProcessor"); return new Answer(cmd, false, "Not currently applicable for KVMStorageProcessor"); } + + /** + * Determine if migration is using host-local source pool. If so, return this host's storage as the template source, + * rather than remote host's + * @param localPool The host-local storage pool being migrated to + * @param migrationOptions The migration options provided with a migrating volume + * @return + */ + public KVMStoragePool getTemplateSourcePoolUsingMigrationOptions(KVMStoragePool localPool, MigrationOptions migrationOptions) { + if (migrationOptions == null) { + throw new CloudRuntimeException("Migration options cannot be null when choosing a storage pool for migration"); + } + + if (migrationOptions.getScopeType().equals(ScopeType.HOST)) { + return localPool; + } + + return storagePoolMgr.getStoragePool(migrationOptions.getSrcPoolType(), migrationOptions.getSrcPoolUuid()); + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index 4ed5dd1b6a4..38604aa861a 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -1282,7 +1282,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { @Override public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) { - return copyPhysicalDisk(disk, name, destPool, timeout, null, null); + return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null); } /** @@ -1292,7 +1292,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { * in ManagementServerImpl shows that the destPool is always a Secondary Storage Pool */ @Override - public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] dstPassphrase) { + public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] dstPassphrase, Storage.ProvisioningType provisioningType) { /** With RBD you can't run qemu-img convert with an existing RBD image as destination diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 9f57083e9a9..2779e679012 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -404,11 +404,11 @@ public class LinstorStorageAdaptor implements StorageAdaptor { @Override public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) { - return copyPhysicalDisk(disk, name, destPool, timeout, null, null); + return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null); } @Override - public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] destPassphrase) + public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType) { s_logger.debug("Linstor: copyPhysicalDisk"); final QemuImg.PhysicalDiskFormat sourceFormat = disk.getFormat(); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java index 6af43d50d24..d8ffb43a138 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java @@ -291,11 +291,11 @@ public class ManagedNfsStorageAdaptor implements StorageAdaptor { @Override public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) { - return copyPhysicalDisk(disk, name, destPool, timeout, null, null); + return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null); } @Override - public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] destPassphrase) { + public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] destPassphrase, ProvisioningType provisioningType) { throw new UnsupportedOperationException("Copying a disk is not supported in this configuration."); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java index ea6c4c92d65..ec453abede9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java @@ -153,6 +153,8 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { /** * ScaleIO doesn't need to communicate with the hypervisor normally to create a volume. This is used only to prepare a ScaleIO data disk for encryption. + * Thin encrypted volumes are provisioned in QCOW2 format, which insulates the guest from zeroes/unallocated blocks in the block device that would + * otherwise show up as garbage data through the encryption layer. As a bonus, encrypted QCOW2 format handles discard. * @param name disk path * @param pool pool * @param format disk format @@ -173,12 +175,32 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { KVMPhysicalDisk disk = getPhysicalDisk(name, pool); - try { - CryptSetup crypt = new CryptSetup(); - crypt.luksFormat(passphrase, CryptSetup.LuksType.LUKS, disk.getPath()); + if (provisioningType.equals(Storage.ProvisioningType.THIN)) { + disk.setFormat(QemuImg.PhysicalDiskFormat.QCOW2); disk.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS); - } catch (CryptSetupException ex) { - throw new CloudRuntimeException("Failed to set up encryption for block device " + disk.getPath(), ex); + try (KeyFile keyFile = new KeyFile(passphrase)){ + QemuImg qemuImg = new QemuImg(0, true, true); + Map options = new HashMap<>(); + List qemuObjects = new ArrayList<>(); + long formattedSize = getUsableBytesFromRawBytes(disk.getSize()); + + options.put("preallocation", QemuImg.PreallocationType.Metadata.toString()); + qemuObjects.add(QemuObject.prepareSecretForQemuImg(disk.getFormat(), disk.getQemuEncryptFormat(), keyFile.toString(), "sec0", options)); + QemuImgFile file = new QemuImgFile(disk.getPath(), formattedSize, disk.getFormat()); + qemuImg.create(file, null, options, qemuObjects); + LOGGER.debug(String.format("Successfully formatted %s as encrypted QCOW2", file.getFileName())); + } catch (QemuImgException | LibvirtException | IOException ex) { + throw new CloudRuntimeException("Failed to set up encrypted QCOW on block device " + disk.getPath(), ex); + } + } else { + try { + CryptSetup crypt = new CryptSetup(); + crypt.luksFormat(passphrase, CryptSetup.LuksType.LUKS, disk.getPath()); + disk.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS); + disk.setFormat(QemuImg.PhysicalDiskFormat.RAW); + } catch (CryptSetupException ex) { + throw new CloudRuntimeException("Failed to set up encryption for block device " + disk.getPath(), ex); + } } return disk; @@ -295,16 +317,20 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { @Override public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) { - return copyPhysicalDisk(disk, name, destPool, timeout, null, null); + return copyPhysicalDisk(disk, name, destPool, timeout, null, null, Storage.ProvisioningType.THIN); } @Override - public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[]dstPassphrase) { + public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[]dstPassphrase, Storage.ProvisioningType provisioningType) { if (Strings.isNullOrEmpty(name) || disk == null || destPool == null) { LOGGER.error("Unable to copy physical disk due to insufficient data"); throw new CloudRuntimeException("Unable to copy physical disk due to insufficient data"); } + if (provisioningType == null) { + provisioningType = Storage.ProvisioningType.THIN; + } + LOGGER.debug("Copy physical disk with size: " + disk.getSize() + ", virtualsize: " + disk.getVirtualSize()+ ", format: " + disk.getFormat()); KVMPhysicalDisk destDisk = destPool.getPhysicalDisk(name); @@ -313,21 +339,12 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { throw new CloudRuntimeException("Failed to find the disk: " + name + " of the storage pool: " + destPool.getUuid()); } - // golden copies of templates should be kept as qcow2 in the PowerFlex storage pool - if (disk.isTemplate()) { - LOGGER.debug("This is a template copy, storing in powerflex as QCOW2"); - destDisk.setFormat(QemuImg.PhysicalDiskFormat.QCOW2); - } else { - LOGGER.debug("This is not a template copy, storing in powerflex as RAW"); - destDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW); - } - destDisk.setVirtualSize(disk.getVirtualSize()); destDisk.setSize(disk.getSize()); QemuImg qemu = null; - QemuImgFile srcFile = null; - QemuImgFile destFile = null; + QemuImgFile srcQemuFile = null; + QemuImgFile destQemuFile = null; String srcKeyName = "sec0"; String destKeyName = "sec1"; List qemuObjects = new ArrayList<>(); @@ -335,46 +352,52 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { CryptSetup cryptSetup = null; try (KeyFile srcKey = new KeyFile(srcPassphrase); KeyFile dstKey = new KeyFile(dstPassphrase)){ - qemu = new QemuImg(timeout, true, true); + qemu = new QemuImg(timeout, provisioningType.equals(Storage.ProvisioningType.FAT), true); String srcPath = disk.getPath(); String destPath = destDisk.getPath(); - QemuImg.PhysicalDiskFormat destFormat = destDisk.getFormat(); + QemuImageOptions qemuImageOpts = new QemuImageOptions(srcPath); + srcQemuFile = new QemuImgFile(srcPath, disk.getFormat()); + destQemuFile = new QemuImgFile(destPath); + + if (disk.isTemplate()) { + destQemuFile.setFormat(QemuImg.PhysicalDiskFormat.QCOW2); + } + if (srcKey.isSet()) { - qemuObjects.add(QemuObject.prepareSecretForQemuImg(disk.getFormat(), null , srcKey.toString(), srcKeyName, options)); + qemuObjects.add(QemuObject.prepareSecretForQemuImg(disk.getFormat(), disk.getQemuEncryptFormat(), srcKey.toString(), srcKeyName, options)); qemuImageOpts = new QemuImageOptions(disk.getFormat(), srcPath, srcKeyName); } if (dstKey.isSet()) { - if (qemu.supportsSkipZeros()) { - // format and open luks device rather than letting qemu do a slow copy of full image - cryptSetup = new CryptSetup(); - cryptSetup.luksFormat(dstPassphrase, CryptSetup.LuksType.LUKS, destDisk.getPath()); - cryptSetup.open(dstPassphrase, CryptSetup.LuksType.LUKS, destDisk.getPath(), name); - destPath = String.format("/dev/mapper/%s", name); + if (!provisioningType.equals(Storage.ProvisioningType.FAT)) { + destDisk.setFormat(QemuImg.PhysicalDiskFormat.QCOW2); + destQemuFile.setFormat(QemuImg.PhysicalDiskFormat.QCOW2); + options.put("preallocation", QemuImg.PreallocationType.Metadata.toString()); } else { - qemuObjects.add(QemuObject.prepareSecretForQemuImg(destDisk.getFormat(), null, dstKey.toString(), destKeyName, options)); - destFormat = QemuImg.PhysicalDiskFormat.LUKS; + qemu.setSkipZero(false); + destDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW); + // qemu-img wants to treat RAW + encrypt formatting as LUKS + destQemuFile.setFormat(QemuImg.PhysicalDiskFormat.LUKS); } + qemuObjects.add(QemuObject.prepareSecretForQemuImg(destDisk.getFormat(), QemuObject.EncryptFormat.LUKS, dstKey.toString(), destKeyName, options)); destDisk.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS); } - srcFile = new QemuImgFile(srcPath, disk.getFormat()); - destFile = new QemuImgFile(destPath, destFormat); + boolean forceSourceFormat = srcQemuFile.getFormat() == QemuImg.PhysicalDiskFormat.RAW; + LOGGER.debug(String.format("Starting copy from source disk %s(%s) to PowerFlex volume %s(%s), forcing source format is %b", srcQemuFile.getFileName(), srcQemuFile.getFormat(), destQemuFile.getFileName(), destQemuFile.getFormat(), forceSourceFormat)); + qemu.convert(srcQemuFile, destQemuFile, options, qemuObjects, qemuImageOpts,null, forceSourceFormat); + LOGGER.debug("Successfully converted source disk image " + srcQemuFile.getFileName() + " to PowerFlex volume: " + destDisk.getPath()); - boolean forceSourceFormat = srcFile.getFormat() == QemuImg.PhysicalDiskFormat.RAW; - LOGGER.debug(String.format("Starting copy from source disk %s(%s) to PowerFlex volume %s(%s), forcing source format is %b", srcFile.getFileName(), srcFile.getFormat(), destFile.getFileName(), destFile.getFormat(), forceSourceFormat)); - if (destFile.getFormat() == QemuImg.PhysicalDiskFormat.QCOW2) { - destFile.setSize(disk.getVirtualSize()); - LOGGER.debug(String.format("Pre-formatting qcow2 block device %s to size %s", destFile.getFileName(), destFile.getSize())); - qemu.create(destFile); + if (destQemuFile.getFormat() == QemuImg.PhysicalDiskFormat.QCOW2 && !disk.isTemplate()) { + QemuImageOptions resizeOptions = new QemuImageOptions(destQemuFile.getFormat(), destPath, destKeyName); + resizeQcow2ToVolume(destPath, resizeOptions, qemuObjects, timeout); + LOGGER.debug("Resized volume at " + destPath); } - qemu.convert(srcFile, destFile, options, qemuObjects, qemuImageOpts,null, forceSourceFormat); - LOGGER.debug("Succesfully converted source disk image " + srcFile.getFileName() + " to PowerFlex volume: " + destDisk.getPath()); - } catch (QemuImgException | LibvirtException | IOException | CryptSetupException e) { + } catch (QemuImgException | LibvirtException | IOException e) { try { - Map srcInfo = qemu.info(srcFile); + Map srcInfo = qemu.info(srcQemuFile); LOGGER.debug("Source disk info: " + Arrays.asList(srcInfo)); } catch (Exception ignored) { LOGGER.warn("Unable to get info from source disk: " + disk.getName()); @@ -479,7 +502,7 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { srcFile = new QemuImgFile(srcTemplateFilePath, srcFileFormat); qemu.info(srcFile); /** - * Even though the disk itself is raw, we store templates on ScaleIO the raw volumes in qcow2 format. + * Even though the disk itself is raw, we store templates on ScaleIO in qcow2 format. * This improves performance by reading/writing less data to volume, saves the unused space for encryption header, and * nicely encapsulates VM images that might contain LUKS data (as opposed to converting to raw which would look like a LUKS volume). */ @@ -516,4 +539,25 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor { throw new CloudRuntimeException("Unable to extract template " + downloadedTemplateFile); } } + + public void resizeQcow2ToVolume(String volumePath, QemuImageOptions options, List objects, Integer timeout) throws QemuImgException, LibvirtException { + long rawSizeBytes = getPhysicalDiskSize(volumePath); + long usableSizeBytes = getUsableBytesFromRawBytes(rawSizeBytes); + QemuImg qemu = new QemuImg(timeout); + qemu.resize(options, objects, usableSizeBytes); + } + + /** + * Calculates usable size from raw size, assuming qcow2 requires 192k/1GB for metadata + * We also remove 32MiB for potential encryption/safety factor. + * @param raw size in bytes + * @return usable size in bytesbytes + */ + public static long getUsableBytesFromRawBytes(Long raw) { + long usable = raw - (32 << 20) - ((raw >> 30) * 200704); + if (usable < 0) { + usable = 0L; + } + return usable; + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java index ca0f77f4512..8944d104958 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java @@ -65,7 +65,7 @@ public interface StorageAdaptor { public List listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool); public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout); - public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] dstPassphrase); + public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] dstPassphrase, Storage.ProvisioningType provisioningType); public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout); diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index 385c53af4f7..0724dfd94e3 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; @@ -32,6 +33,8 @@ import com.cloud.storage.Storage; import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.Script; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + public class QemuImg { public final static String BACKING_FILE = "backing_file"; public final static String BACKING_FILE_FORMAT = "backing_file_format"; @@ -765,4 +768,29 @@ public class QemuImg { public boolean supportsSkipZeros() { return this.skipZero; } + + public void setSkipZero(boolean skipZero) { + this.skipZero = skipZero; + } + + public boolean supportsImageFormat(QemuImg.PhysicalDiskFormat format) { + final Script s = new Script(_qemuImgPath, timeout); + s.add("--help"); + + final OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + String result = s.execute(parser); + String output = parser.getLines(); + + // Older Qemu returns output in result due to --help reporting error status + if (result != null) { + output = result; + } + + return helpSupportsImageFormat(output, format); + } + + protected static boolean helpSupportsImageFormat(String text, QemuImg.PhysicalDiskFormat format) { + Pattern pattern = Pattern.compile("Supported\\sformats:[a-zA-Z0-9-_\\s]*?\\b" + format + "\\b", CASE_INSENSITIVE); + return pattern.matcher(text).find(); + } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index b951f997909..5afe84548a8 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -4850,6 +4850,7 @@ public class LibvirtComputingResourceTest { final DomainState state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING; info.state = state; + when(pool.getType()).thenReturn(StoragePoolType.RBD); when(libvirtComputingResource.getStoragePoolMgr()).thenReturn(storagePoolMgr); when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool); when(storagePool.getPhysicalDisk(path)).thenReturn(vol); @@ -4909,6 +4910,7 @@ public class LibvirtComputingResourceTest { final DomainState state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING; info.state = state; + when(pool.getType()).thenReturn(StoragePoolType.Linstor); when(libvirtComputingResource.getStoragePoolMgr()).thenReturn(storagePoolMgr); when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool); when(storagePool.getPhysicalDisk(path)).thenReturn(vol); @@ -4976,6 +4978,7 @@ public class LibvirtComputingResourceTest { final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); final KVMPhysicalDisk vol = Mockito.mock(KVMPhysicalDisk.class); + when(pool.getType()).thenReturn(StoragePoolType.Filesystem); when(libvirtComputingResource.getStoragePoolMgr()).thenReturn(storagePoolMgr); when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool); when(storagePool.getPhysicalDisk(path)).thenReturn(vol); @@ -5006,6 +5009,7 @@ public class LibvirtComputingResourceTest { final KVMPhysicalDisk vol = Mockito.mock(KVMPhysicalDisk.class); final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class); + when(pool.getType()).thenReturn(StoragePoolType.RBD); when(libvirtComputingResource.getStoragePoolMgr()).thenReturn(storagePoolMgr); when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool); when(storagePool.getPhysicalDisk(path)).thenReturn(vol); @@ -5052,6 +5056,7 @@ public class LibvirtComputingResourceTest { final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); final KVMStoragePool storagePool = Mockito.mock(KVMStoragePool.class); + when(pool.getType()).thenReturn(StoragePoolType.RBD); when(libvirtComputingResource.getStoragePoolMgr()).thenReturn(storagePoolMgr); when(storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(storagePool); when(storagePool.getPhysicalDisk(path)).thenThrow(CloudRuntimeException.class); diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java new file mode 100644 index 00000000000..c06442c6ae3 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.storage; + +import org.junit.Assert; +import org.junit.Test; + +public class ScaleIOStorageAdaptorTest { + @Test + public void getUsableBytesFromRawBytesTest() { + Assert.assertEquals("Overhead calculated for 8Gi size", 8554774528L, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(8L << 30)); + Assert.assertEquals("Overhead calculated for 4Ti size", 4294130925568L, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(4000L << 30)); + Assert.assertEquals("Overhead calculated for 500Gi size", 536737005568L, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(500L << 30)); + Assert.assertEquals("Unsupported small size", 0, ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(1L)); + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index cb7f6919e36..8bb762cca85 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -349,4 +350,22 @@ public class QemuImgTest { df.delete(); } + + @Test + public void testHelpSupportsImageFormat() throws QemuImgException, LibvirtException { + String partialHelp = "Parameters to dd subcommand:\n" + + " 'bs=BYTES' read and write up to BYTES bytes at a time (default: 512)\n" + + " 'count=N' copy only N input blocks\n" + + " 'if=FILE' read from FILE\n" + + " 'of=FILE' write to FILE\n" + + " 'skip=N' skip N bs-sized blocks at the start of input\n" + + "\n" + + "Supported formats: cloop copy-on-read file ftp ftps host_cdrom host_device https iser luks nbd nvme parallels qcow qcow2 qed quorum raw rbd ssh throttle vdi vhdx vmdk vpc vvfat\n" + + "\n" + + "See for how to report bugs.\n" + + "More information on the QEMU project at ."; + Assert.assertTrue("should support luks", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.LUKS)); + Assert.assertTrue("should support qcow2", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.QCOW2)); + Assert.assertFalse("should not support http", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.SHEEPDOG)); + } } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java index 8229b21a51a..ed9df621793 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java @@ -22,6 +22,12 @@ import java.util.Map; import javax.inject.Inject; +import com.cloud.agent.api.storage.ResizeVolumeCommand; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.host.HostVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; @@ -119,6 +125,8 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { private ConfigurationDao configDao; @Inject private HostDao hostDao; + @Inject + private VMInstanceDao vmInstanceDao; public ScaleIOPrimaryDataStoreDriver() { @@ -475,9 +483,9 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { CreateObjectAnswer answer = new CreateObjectAnswer(createdObject.getTO()); - // if volume needs to be set up with encryption, do it now. - if (anyVolumeRequiresEncryption(volumeInfo)) { - LOGGER.debug(String.format("Setting up encryption for volume %s", volumeInfo)); + // if volume needs to be set up with encryption, do it now if it's not a root disk (which gets done during template copy) + if (anyVolumeRequiresEncryption(volumeInfo) && !volumeInfo.getVolumeType().equals(Volume.Type.ROOT)) { + LOGGER.debug(String.format("Setting up encryption for volume %s", volumeInfo.getId())); VolumeObjectTO prepVolume = (VolumeObjectTO) createdObject.getTO(); prepVolume.setPath(volumePath); prepVolume.setUuid(volumePath); @@ -899,6 +907,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { try { String scaleIOVolumeId = ScaleIOUtil.getVolumePath(volumeInfo.getPath()); Long storagePoolId = volumeInfo.getPoolId(); + final ScaleIOGatewayClient client = getScaleIOClient(storagePoolId); ResizeVolumePayload payload = (ResizeVolumePayload)volumeInfo.getpayload(); long newSizeInBytes = payload.newSize != null ? payload.newSize : volumeInfo.getSize(); @@ -907,13 +916,69 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { throw new CloudRuntimeException("Only increase size is allowed for volume: " + volumeInfo.getName()); } - org.apache.cloudstack.storage.datastore.api.Volume scaleIOVolume = null; + org.apache.cloudstack.storage.datastore.api.Volume scaleIOVolume = client.getVolume(scaleIOVolumeId); long newSizeInGB = newSizeInBytes / (1024 * 1024 * 1024); long newSizeIn8gbBoundary = (long) (Math.ceil(newSizeInGB / 8.0) * 8.0); - final ScaleIOGatewayClient client = getScaleIOClient(storagePoolId); - scaleIOVolume = client.resizeVolume(scaleIOVolumeId, (int) newSizeIn8gbBoundary); - if (scaleIOVolume == null) { - throw new CloudRuntimeException("Failed to resize volume: " + volumeInfo.getName()); + + if (scaleIOVolume.getSizeInKb() == newSizeIn8gbBoundary << 20) { + LOGGER.debug("No resize necessary at API"); + } else { + scaleIOVolume = client.resizeVolume(scaleIOVolumeId, (int) newSizeIn8gbBoundary); + if (scaleIOVolume == null) { + throw new CloudRuntimeException("Failed to resize volume: " + volumeInfo.getName()); + } + } + + StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); + boolean attachedRunning = false; + long hostId = 0; + + if (payload.instanceName != null) { + VMInstanceVO instance = vmInstanceDao.findVMByInstanceName(payload.instanceName); + if (instance.getState().equals(VirtualMachine.State.Running)) { + hostId = instance.getHostId(); + attachedRunning = true; + } + } + + if (volumeInfo.getFormat().equals(Storage.ImageFormat.QCOW2) || attachedRunning) { + LOGGER.debug("Volume needs to be resized at the hypervisor host"); + + if (hostId == 0) { + hostId = selector.select(volumeInfo, true).getId(); + } + + HostVO host = hostDao.findById(hostId); + if (host == null) { + throw new CloudRuntimeException("Found no hosts to run resize command on"); + } + + EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(host); + ResizeVolumeCommand resizeVolumeCommand = new ResizeVolumeCommand( + volumeInfo.getPath(), new StorageFilerTO(storagePool), volumeInfo.getSize(), newSizeInBytes, + payload.shrinkOk, payload.instanceName, volumeInfo.getChainInfo(), + volumeInfo.getPassphrase(), volumeInfo.getEncryptFormat()); + + try { + if (!attachedRunning) { + grantAccess(volumeInfo, ep, volumeInfo.getDataStore()); + } + Answer answer = ep.sendMessage(resizeVolumeCommand); + + if (!answer.getResult() && volumeInfo.getFormat().equals(Storage.ImageFormat.QCOW2)) { + throw new CloudRuntimeException("Failed to resize at host: " + answer.getDetails()); + } else if (!answer.getResult()) { + // for non-qcow2, notifying the running VM is going to be best-effort since we can't roll back + // or avoid VM seeing a successful change at the PowerFlex volume after e.g. reboot + LOGGER.warn("Resized raw volume, but failed to notify. VM will see change on reboot. Error:" + answer.getDetails()); + } else { + LOGGER.debug("Resized volume at host: " + answer.getDetails()); + } + } finally { + if (!attachedRunning) { + revokeAccess(volumeInfo, ep, volumeInfo.getDataStore()); + } + } } VolumeVO volume = volumeDao.findById(volumeInfo.getId()); @@ -921,7 +986,6 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { volume.setSize(scaleIOVolume.getSizeInKb() * 1024); volumeDao.update(volume.getId(), volume); - StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); long capacityBytes = storagePool.getCapacityBytes(); long usedBytes = storagePool.getUsedBytes(); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index a5f5f1d2bcb..2ef46b9059b 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1142,7 +1142,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic if (storagePoolId != null) { StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId); - if (storagePoolVO.isManaged()) { + if (storagePoolVO.isManaged() && !storagePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { Long instanceId = volume.getInstanceId(); if (instanceId != null) {