mirror of https://github.com/apache/cloudstack.git
Pass storage scope during KVM volume migration to avoid remotely moun… (#190)
* Use cryptsetup w/o zeroing for encrypted scaleio - faster Signed-off-by: Marcus Sorensen <mls@apple.com> * Pass storage scope during KVM volume migration to avoid remotely mounting local storage Signed-off-by: Marcus Sorensen <mls@apple.com> * Add method to choose template pool based on scope Signed-off-by: Marcus Sorensen <mls@apple.com> * Clean up null check when creating migration options Signed-off-by: Marcus Sorensen <mls@apple.com> * ScaleIO enhancements - thin/thick encrypted, online resize Signed-off-by: Marcus Sorensen <mls@apple.com> Co-authored-by: Marcus Sorensen <mls@apple.com>
This commit is contained in:
parent
557045bced
commit
d4596ddc9a
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<String, String> options = new HashMap<String, String>();
|
||||
List<QemuObject> 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()) {
|
||||
|
|
|
|||
|
|
@ -977,6 +977,8 @@ public class LibvirtVMDef {
|
|||
return _diskFmtType;
|
||||
}
|
||||
|
||||
public void setDiskFormatType(DiskFmtType type) { _diskFmtType = type; }
|
||||
|
||||
public void setBytesReadRate(Long bytesReadRate) {
|
||||
_bytesReadRate = bytesReadRate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Resi
|
|||
@Override
|
||||
public Answer execute(final ResizeVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||
final String volumeId = command.getPath();
|
||||
final long newSize = command.getNewSize();
|
||||
long newSize = command.getNewSize();
|
||||
final long currentSize = command.getCurrentSize();
|
||||
final String vmInstanceName = command.getInstanceName();
|
||||
final boolean shrinkOk = command.getShrinkOk();
|
||||
|
|
@ -80,11 +83,34 @@ public final class LibvirtResizeVolumeCommandWrapper extends CommandWrapper<Resi
|
|||
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
|
||||
KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
|
||||
|
||||
if (spool.getType().equals(StoragePoolType.PowerFlex)) {
|
||||
pool.connectPhysicalDisk(volumeId, null);
|
||||
}
|
||||
|
||||
final KVMPhysicalDisk vol = pool.getPhysicalDisk(volumeId);
|
||||
final String path = vol.getPath();
|
||||
String type = notifyOnlyType;
|
||||
|
||||
if (pool.getType() != StoragePoolType.RBD && pool.getType() != StoragePoolType.Linstor) {
|
||||
if (spool.getType().equals(StoragePoolType.PowerFlex) && vol.getFormat().equals(PhysicalDiskFormat.QCOW2)) {
|
||||
// PowerFlex QCOW2 sizing needs to consider overhead.
|
||||
newSize = ScaleIOStorageAdaptor.getUsableBytesFromRawBytes(newSize);
|
||||
} else if (spool.getType().equals(StoragePoolType.PowerFlex)) {
|
||||
// PowerFlex RAW/LUKS is already resized, we just notify the domain based on new size (considering LUKS overhead)
|
||||
try {
|
||||
QemuImg qemu = new QemuImg(0);
|
||||
QemuImgFile qemuFile = new QemuImgFile(path);
|
||||
Map<String, String> 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<Resi
|
|||
s_logger.debug("Resizing volume: " + path + ", from: " + toHumanReadableSize(currentSize) + ", to: " + toHumanReadableSize(newSize) + ", type: " + type + ", name: " + vmInstanceName + ", shrinkOk: " + shrinkOk);
|
||||
|
||||
/* libvirt doesn't support resizing (C)LVM devices, and corrupts QCOW2 in some scenarios, so we have to do these via qemu-img */
|
||||
if (pool.getType() != StoragePoolType.CLVM && pool.getType() != StoragePoolType.Linstor &&
|
||||
vol.getFormat() != PhysicalDiskFormat.QCOW2) {
|
||||
if (pool.getType() != StoragePoolType.CLVM && pool.getType() != StoragePoolType.Linstor && pool.getType() != StoragePoolType.PowerFlex
|
||||
&& vol.getFormat() != PhysicalDiskFormat.QCOW2) {
|
||||
s_logger.debug("Volume " + path + " can be resized by libvirt. Asking libvirt to resize the volume.");
|
||||
try {
|
||||
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
|
||||
|
|
|
|||
|
|
@ -394,11 +394,11 @@ public class IscsiAdmStorageAdaptor 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 srcDisk, String destVolumeUuid, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] destPassphrase) {
|
||||
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk srcDisk, String destVolumeUuid, KVMStoragePool destPool, int timeout, byte[] srcPassphrase, byte[] destPassphrase, ProvisioningType provisioningType) {
|
||||
|
||||
QemuImgFile srcFile;
|
||||
|
||||
|
|
|
|||
|
|
@ -425,12 +425,12 @@ public class KVMStoragePoolManager {
|
|||
|
||||
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
|
||||
StorageAdaptor adaptor = getStorageAdaptor(destPool.getType());
|
||||
return adaptor.copyPhysicalDisk(disk, name, destPool, timeout, null, null);
|
||||
return adaptor.copyPhysicalDisk(disk, name, destPool, timeout, null, null, null);
|
||||
}
|
||||
|
||||
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) {
|
||||
StorageAdaptor adaptor = getStorageAdaptor(destPool.getType());
|
||||
return adaptor.copyPhysicalDisk(disk, name, destPool, timeout, srcPassphrase, dstPassphrase);
|
||||
return adaptor.copyPhysicalDisk(disk, name, destPool, timeout, srcPassphrase, dstPassphrase, provisioningType);
|
||||
}
|
||||
|
||||
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool, int timeout) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import java.util.UUID;
|
|||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.storage.ScopeType;
|
||||
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
|
||||
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
|
||||
import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand;
|
||||
|
|
@ -415,7 +416,7 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
s_logger.warn("Failed to connect new volume at path: " + path + ", in storage pool id: " + primaryStore.getUuid());
|
||||
}
|
||||
|
||||
vol = storagePoolMgr.copyPhysicalDisk(BaseVol, path != null ? path : volume.getUuid(), primaryPool, cmd.getWaitInMillSeconds(), null, volume.getPassphrase());
|
||||
vol = storagePoolMgr.copyPhysicalDisk(BaseVol, path != null ? path : volume.getUuid(), primaryPool, cmd.getWaitInMillSeconds(), null, volume.getPassphrase(), volume.getProvisioningType());
|
||||
|
||||
storagePoolMgr.disconnectPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), path);
|
||||
} else {
|
||||
|
|
@ -1366,6 +1367,11 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
final String glusterVolume = attachingPool.getSourceDir().replace("/", "");
|
||||
diskdef.defNetworkBasedDisk(glusterVolume + path.replace(mountpoint, ""), attachingPool.getSourceHost(), attachingPool.getSourcePort(), null,
|
||||
null, devId, busT, DiskProtocol.GLUSTER, DiskDef.DiskFmtType.QCOW2);
|
||||
} else if (attachingPool.getType() == StoragePoolType.PowerFlex) {
|
||||
diskdef.defBlockBasedDisk(attachingDisk.getPath(), devId, busT);
|
||||
if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
|
||||
diskdef.setDiskFormatType(DiskDef.DiskFmtType.QCOW2);
|
||||
}
|
||||
} else if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
|
||||
diskdef.defFileBasedDisk(attachingDisk.getPath(), devId, busT, DiskDef.DiskFmtType.QCOW2);
|
||||
} else if (attachingDisk.getFormat() == PhysicalDiskFormat.RAW) {
|
||||
|
|
@ -1552,14 +1558,15 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
}
|
||||
|
||||
MigrationOptions migrationOptions = volume.getMigrationOptions();
|
||||
if (isLinkedCloneMigration(migrationOptions)) {
|
||||
String srcStoreUuid = migrationOptions.getSrcPoolUuid();
|
||||
StoragePoolType srcPoolType = migrationOptions.getSrcPoolType();
|
||||
KVMStoragePool srcPool = storagePoolMgr.getStoragePool(srcPoolType, srcStoreUuid);
|
||||
if (migrationOptions != null) {
|
||||
int timeout = migrationOptions.getTimeout();
|
||||
vol = createLinkedCloneVolume(migrationOptions, srcPool, primaryPool, volume, format, timeout);
|
||||
} else if (isFullCloneMigration(migrationOptions)) {
|
||||
vol = createFullCloneVolume(migrationOptions, volume, primaryPool, format);
|
||||
|
||||
if (migrationOptions.getType() == MigrationOptions.Type.LinkedClone) {
|
||||
KVMStoragePool srcPool = getTemplateSourcePoolUsingMigrationOptions(primaryPool, migrationOptions);
|
||||
vol = createLinkedCloneVolume(migrationOptions, srcPool, primaryPool, volume, format, timeout);
|
||||
} else if (migrationOptions.getType() == MigrationOptions.Type.FullClone) {
|
||||
vol = createFullCloneVolume(migrationOptions, volume, primaryPool, format);
|
||||
}
|
||||
} else {
|
||||
vol = primaryPool.createPhysicalDisk(volume.getUuid(), format,
|
||||
volume.getProvisioningType(), disksize, volume.getPassphrase());
|
||||
|
|
@ -1584,14 +1591,6 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
protected static boolean isLinkedCloneMigration(MigrationOptions options) {
|
||||
return options != null && options.getType() == MigrationOptions.Type.LinkedClone;
|
||||
}
|
||||
|
||||
protected static boolean isFullCloneMigration(MigrationOptions options) {
|
||||
return options != null && options.getType() == MigrationOptions.Type.FullClone;
|
||||
}
|
||||
|
||||
protected static final MessageFormat SnapshotXML = new MessageFormat(" <domainsnapshot>" + " <name>{0}</name>" + " <domain>"
|
||||
+ " <uuid>{1}</uuid>" + " </domain>" + " </domainsnapshot>");
|
||||
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String, String> options = new HashMap<>();
|
||||
List<QemuObject> 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<QemuObject> 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<String, String> srcInfo = qemu.info(srcFile);
|
||||
Map<String, String> 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<QemuObject> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public interface StorageAdaptor {
|
|||
public List<KVMPhysicalDisk> 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://qemu.org/contribute/report-a-bug> for how to report bugs.\n" +
|
||||
"More information on the QEMU project at <https://qemu.org>.";
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue