Enable live volume migration for StorPool and small fixes (#6661)

This commit is contained in:
slavkap 2023-01-10 19:21:39 +02:00 committed by GitHub
parent 9cfebdf6e5
commit b392084950
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 580 additions and 204 deletions

View File

@ -51,6 +51,16 @@
<artifactId>commons-collections4</artifactId>
<version>${cs.commons-collections.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.7.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -25,6 +25,7 @@ import java.io.File;
import java.util.List;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
import org.apache.cloudstack.utils.qemu.QemuImgFile;
@ -106,6 +107,10 @@ public final class StorPoolDownloadTemplateCommandWrapper extends CommandWrapper
final QemuImg qemu = new QemuImg(cmd.getWaitInMillSeconds());
StorPoolStorageAdaptor.resize( Long.toString(srcDisk.getVirtualSize()), dst.getPath());
if (dst instanceof TemplateObjectTO) {
((TemplateObjectTO) dst).setSize(srcDisk.getVirtualSize());
}
dstPath = dst.getPath();
StorPoolStorageAdaptor.attachOrDetachVolume("attach", cmd.getObjectType(), dstPath);

View File

@ -49,7 +49,7 @@ public final class StorPoolModifyStorageCommandWrapper extends CommandWrapper<St
public Answer execute(final StorPoolModifyStoragePoolCommand command, final LibvirtComputingResource libvirtComputingResource) {
String clusterId = getSpClusterId();
if (clusterId == null) {
log.debug(String.format("Could not get StorPool cluster id for a command $s", command.getClass()));
log.debug(String.format("Could not get StorPool cluster id for a command [%s]", command.getClass()));
return new Answer(command, false, "spNotFound");
}
try {
@ -83,8 +83,6 @@ public final class StorPoolModifyStorageCommandWrapper extends CommandWrapper<St
String SP_CLUSTER_ID = null;
final String err = sc.execute(parser);
if (err != null) {
final String errMsg = String.format("Could not execute storpool_confget. Error: %s", err);
log.warn(errMsg);
StorPoolStorageAdaptor.SP_LOG("Could not execute storpool_confget. Error: %s", err);
return SP_CLUSTER_ID;
}

View File

@ -58,7 +58,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, StoragePoolType storagePoolType, Map<String, String> details) {
SP_LOG("StorpooolStorageAdaptor.createStoragePool: uuid=%s, host=%s:%d, path=%s, userInfo=%s, type=%s", uuid, host, port, path, userInfo, storagePoolType);
SP_LOG("StorPoolStorageAdaptor.createStoragePool: uuid=%s, host=%s:%d, path=%s, userInfo=%s, type=%s", uuid, host, port, path, userInfo, storagePoolType);
StorPoolStoragePool storagePool = new StorPoolStoragePool(uuid, host, port, storagePoolType, this);
storageUuidToStoragePool.put(uuid, storagePool);
@ -67,30 +67,30 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public KVMStoragePool getStoragePool(String uuid) {
SP_LOG("StorpooolStorageAdaptor.getStoragePool: uuid=%s", uuid);
SP_LOG("StorPoolStorageAdaptor.getStoragePool: uuid=%s", uuid);
return storageUuidToStoragePool.get(uuid);
}
@Override
public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
SP_LOG("StorpooolStorageAdaptor.getStoragePool: uuid=%s, refresh=%s", uuid, refreshInfo);
SP_LOG("StorPoolStorageAdaptor.getStoragePool: uuid=%s, refresh=%s", uuid, refreshInfo);
return storageUuidToStoragePool.get(uuid);
}
@Override
public boolean deleteStoragePool(String uuid) {
SP_LOG("StorpooolStorageAdaptor.deleteStoragePool: uuid=%s", uuid);
SP_LOG("StorPoolStorageAdaptor.deleteStoragePool: uuid=%s", uuid);
return storageUuidToStoragePool.remove(uuid) != null;
}
@Override
public boolean deleteStoragePool(KVMStoragePool pool) {
SP_LOG("StorpooolStorageAdaptor.deleteStoragePool: uuid=%s", pool.getUuid());
SP_LOG("StorPoolStorageAdaptor.deleteStoragePool: uuid=%s", pool.getUuid());
return deleteStoragePool(pool.getUuid());
}
private static long getDeviceSize(final String devPath) {
SP_LOG("StorpooolStorageAdaptor.getDeviceSize: path=%s", devPath);
SP_LOG("StorPoolStorageAdaptor.getDeviceSize: path=%s", devPath);
if (getVolumeNameFromPath(devPath, true) == null) {
return 0;
@ -149,7 +149,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
return false;
}
SP_LOG("StorpooolStorageAdaptor.attachOrDetachVolume: cmd=%s, type=%s, uuid=%s, name=%s", command, type, volumeUuid, name);
SP_LOG("StorPoolStorageAdaptor.attachOrDetachVolume: cmd=%s, type=%s, uuid=%s, name=%s", command, type, volumeUuid, name);
final int numTries = 10;
final int sleepTime = 1000;
@ -205,7 +205,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
return false;
}
SP_LOG("StorpooolStorageAdaptor.resize: size=%s, uuid=%s, name=%s", newSize, volumeUuid, name);
SP_LOG("StorPoolStorageAdaptor.resize: size=%s, uuid=%s, name=%s", newSize, volumeUuid, name);
Script sc = new Script("storpool", 0, log);
sc.add("-M");
@ -230,7 +230,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
SP_LOG("StorpooolStorageAdaptor.getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
SP_LOG("StorPoolStorageAdaptor.getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
log.debug(String.format("getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
@ -245,7 +245,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
SP_LOG("StorpooolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
SP_LOG("StorPoolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
log.debug(String.format("connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
@ -254,7 +254,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public boolean disconnectPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
SP_LOG("StorPoolStorageAdaptor.disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
log.debug(String.format("disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
return attachOrDetachVolume("detach", "volume", volumeUuid);
@ -262,32 +262,23 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
String volumeUuid = volumeToDisconnect.get(DiskTO.UUID);
SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDisk: map. uuid=%s", volumeUuid);
log.debug(String.format("StorPoolStorageAdaptor.disconnectPhysicalDisk: map. uuid=%s", volumeUuid));
return attachOrDetachVolume("detach", "volume", volumeUuid);
}
@Override
public boolean disconnectPhysicalDiskByPath(String localPath) {
SP_LOG("StorpooolStorageAdaptor.disconnectPhysicalDiskByPath: localPath=%s", localPath);
log.debug(String.format("disconnectPhysicalDiskByPath: localPath=%s", localPath));
return attachOrDetachVolume("detach", "volume", localPath);
}
// The following do not apply for StorpoolStorageAdaptor?
@Override
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, KVMStoragePool pool, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
SP_LOG("StorpooolStorageAdaptor.createPhysicalDisk: uuid=%s, pool=%s, format=%s, size=%d", volumeUuid, pool, format, size);
throw new UnsupportedOperationException("Creating a physical disk is not supported.");
}
@Override
public boolean deletePhysicalDisk(String volumeUuid, KVMStoragePool pool, Storage.ImageFormat format) {
// Should only come here when cleaning-up StorPool snapshots associated with CloudStack templates.
SP_LOG("StorpooolStorageAdaptor.deletePhysicalDisk: uuid=%s, pool=%s, format=%s", volumeUuid, pool, format);
SP_LOG("StorPoolStorageAdaptor.deletePhysicalDisk: uuid=%s, pool=%s, format=%s", volumeUuid, pool, format);
final String name = getVolumeNameFromPath(volumeUuid, true);
if (name == null) {
final String err = String.format("StorpooolStorageAdaptor.deletePhysicalDisk: '%s' is not a StorPool volume?", volumeUuid);
final String err = String.format("StorPoolStorageAdaptor.deletePhysicalDisk: '%s' is not a StorPool volume?", volumeUuid);
SP_LOG(err);
throw new UnsupportedOperationException(err);
}
@ -311,21 +302,13 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool) {
SP_LOG("StorpooolStorageAdaptor.listPhysicalDisks: uuid=%s, pool=%s", storagePoolUuid, pool);
SP_LOG("StorPoolStorageAdaptor.listPhysicalDisks: uuid=%s, pool=%s", storagePoolUuid, pool);
throw new UnsupportedOperationException("Listing disks is not supported for this configuration.");
}
@Override
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format,
ProvisioningType provisioningType, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) {
SP_LOG("StorpooolStorageAdaptor.createDiskFromTemplate: template=%s, name=%s, fmt=%s, ptype=%s, size=%d, dst_pool=%s, to=%d",
template, name, format, provisioningType, size, destPool.getUuid(), timeout);
throw new UnsupportedOperationException("Creating a disk from a template is not yet supported for this configuration.");
}
@Override
public KVMPhysicalDisk createTemplateFromDisk(KVMPhysicalDisk disk, String name, PhysicalDiskFormat format, long size, KVMStoragePool destPool) {
SP_LOG("StorpooolStorageAdaptor.createTemplateFromDisk: disk=%s, name=%s, fmt=%s, size=%d, dst_pool=%s", disk, name, format, size, destPool.getUuid());
SP_LOG("StorPoolStorageAdaptor.createTemplateFromDisk: disk=%s, name=%s, fmt=%s, size=%d, dst_pool=%s", disk, name, format, size, destPool.getUuid());
throw new UnsupportedOperationException("Creating a template from a disk is not yet supported for this configuration.");
}
@ -336,51 +319,27 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
@Override
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
SP_LOG("StorpooolStorageAdaptor.copyPhysicalDisk: disk=%s, name=%s, dst_pool=%s, to=%d", disk, name, destPool.getUuid(), timeout);
SP_LOG("StorPoolStorageAdaptor.copyPhysicalDisk: disk=%s, name=%s, dst_pool=%s, to=%d", disk, name, destPool.getUuid(), timeout);
throw new UnsupportedOperationException("Copying a disk is not supported in this configuration.");
}
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
SP_LOG("StorpooolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, snapshotName, name, destPool.getUuid());
SP_LOG("StorPoolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, snapshotName, name, destPool.getUuid());
throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration.");
}
@Override
public boolean refresh(KVMStoragePool pool) {
SP_LOG("StorpooolStorageAdaptor.refresh: pool=%s", pool);
SP_LOG("StorPoolStorageAdaptor.refresh: pool=%s", pool);
return true;
}
@Override
public boolean createFolder(String uuid, String path) {
SP_LOG("StorpooolStorageAdaptor.createFolder: uuid=%s, path=%s", uuid, path);
SP_LOG("StorPoolStorageAdaptor.createFolder: uuid=%s, path=%s", uuid, path);
throw new UnsupportedOperationException("A folder cannot be created in this configuration.");
}
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name,
KVMStoragePool destPool, int timeout) {
SP_LOG("StorpooolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot,
snapshotName, name, destPool.getUuid());
throw new UnsupportedOperationException(
"Creating a disk from a snapshot is not supported in this configuration.");
}
public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name,
PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) {
SP_LOG("StorpooolStorageAdaptor.createDiskFromTemplateBacking: template=%s, name=%s, dst_pool=%s", template,
name, destPool.getUuid());
throw new UnsupportedOperationException(
"Creating a disk from a template is not supported in this configuration.");
}
public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, KVMStoragePool destPool,
boolean isIso) {
SP_LOG("StorpooolStorageAdaptor.createTemplateFromDirectDownloadFile: templateFilePath=%s, dst_pool=%s",
templateFilePath, destPool.getUuid());
throw new UnsupportedOperationException(
"Creating a template from direct download is not supported in this configuration.");
}
public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath,
KVMStoragePool destPool, ImageFormat format, int timeout) {
return null;
@ -390,4 +349,22 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
public boolean createFolder(String uuid, String path, String localPath) {
return false;
}
@Override
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, PhysicalDiskFormat format,
ProvisioningType provisioningType, long size, byte[] passphrase) {
return null;
}
@Override
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format,
ProvisioningType provisioningType, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) {
return null;
}
@Override
public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name,
PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) {
return null;
}
}

View File

@ -17,10 +17,47 @@
* under the License.
*/
package org.apache.cloudstack.storage.datastore.driver;
import java.util.Map;
import javax.inject.Inject;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.host.Host;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceTag.ResourceObjectType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateDetailVO;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.VMTemplateDetailsDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.tags.dao.ResourceTagDao;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineManager;
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;
@ -58,45 +95,8 @@ import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand;
import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand;
import com.cloud.agent.api.storage.StorPoolCopyVolumeToSecondaryCommand;
import com.cloud.agent.api.storage.StorPoolDownloadTemplateCommand;
import com.cloud.agent.api.storage.StorPoolDownloadVolumeCommand;
import com.cloud.agent.api.storage.StorPoolResizeVolumeCommand;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.host.Host;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceTag.ResourceObjectType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateDetailVO;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VolumeDetailVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.SnapshotDetailsDao;
import com.cloud.storage.dao.SnapshotDetailsVO;
import com.cloud.storage.dao.VMTemplateDetailsDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.tags.dao.ResourceTagDao;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.VMInstanceDao;
import javax.inject.Inject;
import java.util.Map;
public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@ -133,7 +133,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Inject
private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject
private VMTemplatePoolDao vmTemplatePoolDao;
private StoragePoolHostDao storagePoolHostDao;
@Override
public Map<String, String> getCapabilities() {
@ -629,33 +629,26 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
final String name = vinfo.getUuid();
SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
Long snapshotSize = StorPoolUtil.snapshotSize(parentName, conn);
if (snapshotSize == null) {
err = String.format("Snapshot=%s does not exist on StorPool. Will recreate it first on primary", parentName);
vmTemplatePoolDao.remove(templStoragePoolVO.getId());
Long snapshotSize = templStoragePoolVO.getTemplateSize();
long size = vinfo.getSize();
if (snapshotSize != null && size < snapshotSize) {
StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize));
size = snapshotSize;
}
if (err == null) {
long size = vinfo.getSize();
if( size < snapshotSize )
{
StorPoolUtil.spLog(String.format("provided size is too small for snapshot. Provided %d, snapshot %d. Using snapshot size", size, snapshotSize));
size = snapshotSize;
}
StorPoolUtil.spLog(String.format("volume size is: %d", size));
Long vmId = vinfo.getInstanceId();
SpApiResponse resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId),
getVcPolicyTag(vmId), "volume", vinfo.getMaxIops(), conn);
if (resp.getError() == null) {
updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize());
StorPoolUtil.spLog(String.format("volume size is: %d", size));
Long vmId = vinfo.getInstanceId();
SpApiResponse resp = StorPoolUtil.volumeCreate(name, parentName, size, getVMInstanceUUID(vmId),
getVcPolicyTag(vmId), "volume", vinfo.getMaxIops(), conn);
if (resp.getError() == null) {
updateStoragePool(dstData.getDataStore().getId(), vinfo.getSize());
VolumeObjectTO to = (VolumeObjectTO) vinfo.getTO();
to.setSize(vinfo.getSize());
to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
VolumeObjectTO to = (VolumeObjectTO) vinfo.getTO();
to.setSize(vinfo.getSize());
to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false)));
answer = new CopyCmdAnswer(to);
} else {
err = String.format("Could not create Storpool volume %s. Error: %s", name, resp.getError());
}
answer = new CopyCmdAnswer(to);
} else {
err = String.format("Could not create Storpool volume %s. Error: %s", name, resp.getError());
}
} else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.VOLUME) {
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.copyAsync src Data Store=%s", srcData.getDataStore().getDriver());
@ -684,9 +677,9 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
EndPoint ep = selector.select(srcData, dstData);
if( ep == null) {
StorPoolUtil.spLog("select(srcData, dstData) returned NULL. trying srcOnly");
ep = selector.select(srcData); // Storpool is zone
if (ep == null || storagePoolHostDao.findByPoolHost(dstData.getId(), ep.getId()) == null) {
StorPoolUtil.spLog("select(srcData, dstData) returned NULL or the destination pool is not connected to the selected host. Trying dstData");
ep = selector.select(dstData); // Storpool is zone
}
if (ep == null) {
err = "No remote endpoint to send command, check if host or ssvm is down?";
@ -711,27 +704,15 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
} else {
// download volume - first copies to secondary
VolumeObjectTO srcTO = (VolumeObjectTO)srcData.getTO();
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc SRC path=%s ", srcTO.getPath());
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST canonicalName=%s ", dstData.getDataStore().getClass().getCanonicalName());
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc SRC path=%s DST canonicalName=%s ", srcTO.getPath(), dstData.getDataStore().getClass().getCanonicalName());
PrimaryDataStoreTO checkStoragePool = dstData.getTO().getDataStore() instanceof PrimaryDataStoreTO ? (PrimaryDataStoreTO)dstData.getTO().getDataStore() : null;
final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST tmpSnapName=%s ,srcUUID=%s", name, srcTO.getUuid());
final String volumeName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
if (checkStoragePool != null && checkStoragePool.getPoolType().equals(StoragePoolType.StorPool)) {
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
String baseOn = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
//uuid tag will be the same as srcData.uuid
String volumeName = srcData.getUuid();
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc volumeName=%s, baseOn=%s", volumeName, baseOn);
final SpApiResponse response = StorPoolUtil.volumeCopy(volumeName, baseOn, "volume", srcInfo.getMaxIops(), conn);
srcTO.setSize(srcData.getSize());
srcTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(response, false)));
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc DST to=%s", srcTO);
answer = new CopyCmdAnswer(srcTO);
answer = migrateVolumeToStorPool(srcData, dstData, srcInfo, srcTO, volumeName);
} else {
SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
final SpApiResponse resp = StorPoolUtil.volumeSnapshot(name, srcTO.getUuid(), srcInfo.getInstanceId() != null ? getVMInstanceUUID(srcInfo.getInstanceId()) : null, "temporary", null, conn);
final SpApiResponse resp = StorPoolUtil.volumeSnapshot(volumeName, srcTO.getUuid(), srcInfo.getInstanceId() != null ? getVMInstanceUUID(srcInfo.getInstanceId()) : null, "temporary", null, conn);
String snapshotName = StorPoolUtil.getSnapshotNameFromResponse(resp, true, StorPoolUtil.GLOBAL_ID);
if (resp.getError() == null) {
srcTO.setPath(StorPoolUtil.devPath(
@ -794,6 +775,96 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
callback.complete(res);
}
/**
* Live migrate/copy volume from one StorPool storage to another
* @param srcData The source volume data
* @param dstData The destination volume data
* @param srcInfo The source volume info
* @param srcTO The source Volume TO
* @param volumeName The name of the volume
* @return Answer
*/
private Answer migrateVolumeToStorPool(DataObject srcData, DataObject dstData, VolumeInfo srcInfo,
VolumeObjectTO srcTO, final String volumeName) {
Answer answer;
SpConnectionDesc conn = StorPoolUtil.getSpConnection(dstData.getDataStore().getUuid(), dstData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
String baseOn = StorPoolStorageAdaptor.getVolumeNameFromPath(srcTO.getPath(), true);
String vmUuid = null;
String vcPolicyTag = null;
VMInstanceVO vm = null;
if (srcInfo.getInstanceId() != null) {
vm = vmInstanceDao.findById(srcInfo.getInstanceId());
}
if (vm != null) {
vmUuid = vm.getUuid();
vcPolicyTag = getVcPolicyTag(vm.getId());
}
if (vm != null && vm.getState().equals(State.Running)) {
answer = migrateVolume(srcData, dstData, volumeName, conn);
} else {
answer = copyVolume(srcInfo, srcTO, conn, baseOn, vmUuid, vcPolicyTag);
}
return answer;
}
/**
* Copy the volume from StorPool primary storage to another StorPool primary storage
* @param srcInfo The source volume info
* @param srcTO The source Volume TO
* @param conn StorPool connection
* @param baseOn The name of an already existing volume that the new volume is to be a copy of.
* @param vmUuid The UUID of the VM
* @param vcPolicyTag The VC policy tag
* @return Answer
*/
private Answer copyVolume(VolumeInfo srcInfo, VolumeObjectTO srcTO, SpConnectionDesc conn, String baseOn, String vmUuid, String vcPolicyTag) {
//uuid tag will be the same as srcData.uuid
String volumeName = srcInfo.getUuid();
Long iops = (srcInfo.getMaxIops() != null && srcInfo.getMaxIops().longValue() > 0) ? srcInfo.getMaxIops() : null;
SpApiResponse response = StorPoolUtil.volumeCopy(volumeName, baseOn, "volume", iops, vmUuid, vcPolicyTag, conn);
if (response.getError() != null) {
return new CopyCmdAnswer(String.format("Could not copy volume [%s] due to %s", baseOn, response.getError()));
}
String newVolume = StorPoolUtil.getNameFromResponse(response, false);
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc copy volume[%s] from pool[%s] with a new name [%s]",
baseOn, srcInfo.getDataStore().getName(), newVolume);
srcTO.setSize(srcInfo.getSize());
srcTO.setPath(StorPoolUtil.devPath(newVolume));
return new CopyCmdAnswer(srcTO);
}
/**
* Live migrate the StorPool's volume to another StorPool template
* @param srcData The source data volume
* @param dstData The destination data volume
* @param name The volume's name
* @param conn StorPool's connection
* @return Answer
*/
private Answer migrateVolume(DataObject srcData, DataObject dstData, String name, SpConnectionDesc conn) {
Answer answer;
SpApiResponse resp = StorPoolUtil.volumeUpdateTemplate(name, conn);
if (resp.getError() != null) {
answer = new Answer(null, false, String.format("Could not migrate volume %s to %s due to %s", name, conn.getTemplateName(), resp.getError()));
} else {
StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc migrate volume[%s] from pool[%s] to pool[%s]",
name, srcData.getDataStore().getName(),dstData.getDataStore().getName());
VolumeVO updatedVolume = volumeDao.findById(srcData.getId());
updatedVolume.setPoolId(dstData.getDataStore().getId());
updatedVolume.setLastPoolId(srcData.getDataStore().getId());
volumeDao.update(updatedVolume.getId(), updatedVolume);
answer = new Answer(null, true, null);
}
return answer;
}
@Override
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
String snapshotName = snapshot.getUuid();
@ -883,7 +954,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
if (vinfo.getMaxIops() != null) {
response = StorPoolUtil.volumeUpadateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
response = StorPoolUtil.volumeUpdateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
if (response.getError() != null) {
StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s", response.getError().getDescr());
}
@ -942,7 +1013,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
VMInstanceVO userVM = vmInstanceDao.findById(vmId);
SpApiResponse resp = StorPoolUtil.volumeUpadateTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId));
SpApiResponse resp = StorPoolUtil.volumeUpdateTags(volName, volume.getInstanceId() != null ? userVM.getUuid() : "", null, conn, getVcPolicyTag(vmId));
if (resp.getError() != null) {
log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid()));
}
@ -965,7 +1036,7 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
try {
SpConnectionDesc conn = StorPoolUtil.getSpConnection(poolVO.getUuid(), poolVO.getId(), storagePoolDetailsDao, primaryStoreDao);
String volName = StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
SpApiResponse resp = StorPoolUtil.volumeUpadateVCTags(volName, conn, getVcPolicyTag(vmId));
SpApiResponse resp = StorPoolUtil.volumeUpdateVCTags(volName, conn, getVcPolicyTag(vmId));
if (resp.getError() != null) {
log.warn(String.format("Could not update VC policy tags of a volume with id [%s]", volume.getUuid()));
}

View File

@ -37,6 +37,7 @@ import org.apache.cloudstack.storage.datastore.util.StorPoolHelper;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
@ -118,21 +119,22 @@ public class StorPoolHostListener implements HypervisorHostListener {
final Answer answer = agentMgr.easySend(hostId, cmd);
StoragePoolHostVO poolHost = storagePoolHostDao.findByPoolHost(pool.getId(), hostId);
boolean isPoolConnectedToTheHost = poolHost != null;
if (answer == null) {
StorPoolUtil.spLog("Storage pool [%s] is not connected to the host [%s]", poolVO.getName(), host.getName());
deleteVolumeWhenHostCannotConnectPool(conn, volumeOnPool);
removePoolOnHost(poolHost, isPoolConnectedToTheHost);
throw new CloudRuntimeException("Unable to get an answer to the modify storage pool command" + pool.getId());
}
if (!answer.getResult()) {
if (answer.getDetails() != null) {
if (answer.getDetails().equals("objectDoesNotExist")) {
StorPoolUtil.volumeDelete(StorPoolStorageAdaptor.getVolumeNameFromPath(volumeOnPool.getValue(), true), conn);
storagePoolDetailsDao.remove(volumeOnPool.getId());
return false;
} else if (answer.getDetails().equals("spNotFound")) {
return false;
}
StorPoolUtil.spLog("Storage pool [%s] is not connected to the host [%s]", poolVO.getName(), host.getName());
removePoolOnHost(poolHost, isPoolConnectedToTheHost);
if (answer.getDetails() != null && isStorPoolVolumeOrStorageNotExistsOnHost(answer)) {
deleteVolumeWhenHostCannotConnectPool(conn, volumeOnPool);
return false;
}
String msg = "Unable to attach storage pool" + poolId + " to the host" + hostId;
alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
@ -140,8 +142,6 @@ public class StorPoolHostListener implements HypervisorHostListener {
pool.getId());
}
StorPoolUtil.spLog("hostConnect: hostId=%d, poolId=%d", hostId, poolId);
StorPoolModifyStoragePoolAnswer mspAnswer = (StorPoolModifyStoragePoolAnswer)answer;
if (mspAnswer.getLocalDatastoreName() != null && pool.isShared()) {
String datastoreName = mspAnswer.getLocalDatastoreName();
@ -155,7 +155,7 @@ public class StorPoolHostListener implements HypervisorHostListener {
}
}
if (poolHost == null) {
if (!isPoolConnectedToTheHost) {
poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
storagePoolHostDao.persist(poolHost);
} else {
@ -164,10 +164,25 @@ public class StorPoolHostListener implements HypervisorHostListener {
StorPoolHelper.setSpClusterIdIfNeeded(hostId, mspAnswer.getClusterId(), clusterDao, hostDao, clusterDetailsDao);
log.info("Connection established between storage pool " + pool + " and host " + hostId);
StorPoolUtil.spLog("Connection established between storage pool [%s] and host [%s]", poolVO.getName(), host.getName());
return true;
}
private boolean isStorPoolVolumeOrStorageNotExistsOnHost(final Answer answer) {
return StringUtils.equalsAny(answer.getDetails(), "objectDoesNotExist", "spNotFound");
}
private void deleteVolumeWhenHostCannotConnectPool(SpConnectionDesc conn, StoragePoolDetailVO volumeOnPool) {
StorPoolUtil.volumeDelete(StorPoolStorageAdaptor.getVolumeNameFromPath(volumeOnPool.getValue(), true), conn);
storagePoolDetailsDao.remove(volumeOnPool.getId());
}
private void removePoolOnHost(StoragePoolHostVO poolHost, boolean isPoolConnectedToTheHost) {
if (isPoolConnectedToTheHost) {
storagePoolHostDao.remove(poolHost.getId());
}
}
private synchronized StoragePoolDetailVO verifyVolumeIsOnCluster(long poolId, SpConnectionDesc conn, long clusterId) {
StoragePoolDetailVO volumeOnPool = storagePoolDetailsDao.findDetail(poolId, StorPoolUtil.SP_VOLUME_ON_CLUSTER + "-" + clusterId);
if (volumeOnPool == null) {

View File

@ -18,24 +18,16 @@
*/
package org.apache.cloudstack.storage.datastore.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
@ -54,16 +46,23 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.log4j.Logger;
import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class StorPoolUtil {
private static final Logger log = Logger.getLogger(StorPoolUtil.class);
@ -467,7 +466,7 @@ public class StorPoolUtil {
return POST("MultiCluster/VolumeCreate", json, conn);
}
public static SpApiResponse volumeCopy(final String name, final String baseOn, String csTag, Long iops,
public static SpApiResponse volumeCopy(final String name, final String baseOn, String csTag, Long iops, String cvmTag, String vcPolicyTag,
SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("baseOn", baseOn);
@ -475,7 +474,7 @@ public class StorPoolUtil {
json.put("iops", iops);
}
json.put("template", conn.getTemplateName());
Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, null, csTag, null);
Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, cvmTag, csTag, vcPolicyTag);
json.put("tags", tags);
return POST("MultiCluster/VolumeCreate", json, conn);
}
@ -501,7 +500,7 @@ public class StorPoolUtil {
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpadateTags(final String name, final String uuid, Long iops,
public static SpApiResponse volumeUpdateTags(final String name, final String uuid, Long iops,
SpConnectionDesc conn, String vcPolicy) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy);
@ -510,20 +509,26 @@ public class StorPoolUtil {
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpadateCvmTags(final String name, final String uuid, SpConnectionDesc conn) {
public static SpApiResponse volumeUpdateCvmTags(final String name, final String uuid, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, null);
json.put("tags", tags);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpadateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) {
public static SpApiResponse volumeUpdateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, null, vcPolicy);
json.put("tags", tags);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpdateTemplate(final String name, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("template", conn.getTemplateName());
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeSnapshot(final String volumeName, final String snapshotName, String vmUuid,
String csTag, String vcPolicy, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();

View File

@ -335,7 +335,7 @@ public class StorPoolVMSnapshotStrategy extends DefaultVMSnapshotStrategy {
}
VolumeInfo vinfo = volFactory.getVolume(volumeObjectTO.getId());
if (vinfo.getMaxIops() != null) {
resp = StorPoolUtil.volumeUpadateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
resp = StorPoolUtil.volumeUpdateTags(volumeName, null, vinfo.getMaxIops(), conn, null);
if (resp.getError() != null) {
StorPoolUtil.spLog("Volume was reverted successfully but max iops could not be set due to %s",

View File

@ -0,0 +1,245 @@
/*
* 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 org.apache.cloudstack.storage.datastore.driver;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.tags.dao.ResourceTagDao;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.util.StorPoolUtil;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.powermock.core.classloader.annotations.PrepareForTest;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mock;
@RunWith(MockitoJUnitRunner.class)
@PrepareForTest(StorPoolUtil.class)
public class StorPoolPrimaryDataStoreDriverTest {
@Mock
private VMInstanceDao vmInstanceDao;
@Mock
private ResourceTagDao _resourceTagDao;
@Mock
private AsyncCompletionCallback<CopyCommandResult> callback;
@Mock
private PrimaryDataStoreDao storagePool;
@Mock
private StoragePoolDetailsDao detailsDao;
@Mock
private VolumeDao volumeDao;
DataStore srcStore;
DataStore destStore;
DataObject srcObj;
DataObject destObj;
VolumeObjectTO srcTO;
VolumeObjectTO dstTO;
PrimaryDataStoreTO dstPrimaryTo;
MockedStatic<StorPoolUtil> utilities;
StorPoolUtil.SpConnectionDesc conn;
@Before
public void setUp(){
utilities = Mockito.mockStatic(StorPoolUtil.class);
conn = new StorPoolUtil.SpConnectionDesc("1.1.1.1:81", "123", "tmp");
srcStore = mock(DataStore.class);
destStore = mock(DataStore.class);
srcObj = mock(VolumeInfo.class);
destObj = mock(VolumeInfo.class);
srcTO = mock(VolumeObjectTO.class);
dstTO = mock(VolumeObjectTO.class);
dstPrimaryTo = mock(PrimaryDataStoreTO.class);
}
@After
public void tearDown(){
utilities.close();
}
@InjectMocks
private StorPoolPrimaryDataStoreDriver storPoolPrimaryDataStoreDriver;
@Test
public void testMigrateVolumePassed(){
VMInstanceVO vm = mock(VMInstanceVO.class);
setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
when(vm.getState()).thenReturn(State.Running);
when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
StorPoolUtil.SpApiResponse resp = new StorPoolUtil.SpApiResponse();
when(StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn)).thenReturn(resp);
when(volumeDao.findById(srcObj.getId())).thenReturn(mock(VolumeVO.class));
storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
utilities.verify(() -> StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn), times(1));
Assert.assertNull(resp.getError());
}
@Test
public void testMigrateVolumeNotPassed() {
VMInstanceVO vm = mock(VMInstanceVO.class);
setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
when(vm.getState()).thenReturn(State.Running);
when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
StorPoolUtil.SpApiResponse resp = new StorPoolUtil.SpApiResponse();
setResponseError(resp);
when(StorPoolUtil.volumeUpdateTemplate("~t.t.t", conn)).thenReturn(resp);
storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
Assert.assertNotNull(resp.getError());
}
@Test
public void testCopyVolumeAttachedToVmPassed() {
VMInstanceVO vm = mock(VMInstanceVO.class);
setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
when(vm.getState()).thenReturn(State.Stopped);
String vmUuid = UUID.randomUUID().toString();
when(vm.getUuid()).thenReturn(vmUuid);
when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
String volumeUuid = UUID.randomUUID().toString();
when(srcObj.getUuid()).thenReturn(volumeUuid);
when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, vmUuid, "", conn)).thenReturn(response);
storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
Assert.assertNull(response.getError());
}
@Test
public void testCopyVolumeAttachedToVmNotPassed() {
VMInstanceVO vm = mock(VMInstanceVO.class);
setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, vm);
when(vm.getState()).thenReturn(State.Stopped);
String vmUuid = UUID.randomUUID().toString();
when(vm.getUuid()).thenReturn(vmUuid);
when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
setResponseError(response);
String volumeUuid = UUID.randomUUID().toString();
when(srcObj.getUuid()).thenReturn(volumeUuid);
when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, vmUuid, "", conn)).thenReturn(response);
storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
Assert.assertNotNull(response.getError());
}
@Test
public void testCopyVolumeNotAttachedToVmNotPassed() {
setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, null);
when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
setResponseError(response);
String volumeUuid = UUID.randomUUID().toString();
when(srcObj.getUuid()).thenReturn(volumeUuid);
when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn)).thenReturn(response);
storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
utilities.verify(() -> StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn), times(1));
Assert.assertNotNull(response.getError());
}
@Test
public void testCopyVolumeNotAttachedToVmPassed() {
setReturnsWhenSourceAndDestinationAreVolumes(srcStore, destStore, srcObj, destObj, srcTO, dstTO, dstPrimaryTo, null);
when(StorPoolUtil.getSpConnection(destObj.getDataStore().getUuid(), destObj.getDataStore().getId(), detailsDao, storagePool)).thenReturn(conn);
StorPoolUtil.SpApiResponse response = new StorPoolUtil.SpApiResponse();
String volumeUuid = UUID.randomUUID().toString();
when(srcObj.getUuid()).thenReturn(volumeUuid);
when(StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn)).thenReturn(response);
storPoolPrimaryDataStoreDriver.copyAsync(srcObj, destObj, callback);
utilities.verify(() -> StorPoolUtil.volumeCopy(volumeUuid, "~t.t.t", "volume", null, null, null, conn), times(1));
Assert.assertNull(response.getError());
}
private void setReturnsWhenSourceAndDestinationAreVolumes(DataStore srcStore, DataStore destStore, DataObject srcObj, DataObject destObj, VolumeObjectTO srcTO, VolumeObjectTO dstTO, PrimaryDataStoreTO dstPrimaryTo, VMInstanceVO vm) {
when(srcStore.getRole()).thenReturn(DataStoreRole.Primary);
when(destStore.getRole()).thenReturn(DataStoreRole.Primary);
when(srcObj.getDataStore()).thenReturn(srcStore);
when(destObj.getDataStore()).thenReturn(destStore);
when(srcObj.getType()).thenReturn(DataObjectType.VOLUME);
when(destObj.getType()).thenReturn(DataObjectType.VOLUME);
when(destObj.getTO()).thenReturn(dstTO);
when(srcObj.getTO()).thenReturn(srcTO);
when(srcObj.getDataStore().getDriver()).thenReturn(storPoolPrimaryDataStoreDriver);
when(destObj.getTO().getDataStore()).thenReturn(dstPrimaryTo);
when(destObj.getDataStore().getUuid()).thenReturn("SP_API_HTTP=1.1.1.1:81;SP_AUTH_TOKEN=token;SP_TEMPLATE=template_name");
when(destObj.getDataStore().getId()).thenReturn(1L);
when(srcTO.getPath()).thenReturn("/dev/storpool-byid/t.t.t");
when(dstPrimaryTo.getPoolType()).thenReturn(StoragePoolType.StorPool);
when(vmInstanceDao.findById(anyLong())).thenReturn(vm);
}
private static void setResponseError(StorPoolUtil.SpApiResponse resp) {
StorPoolUtil.SpApiError respErr = new StorPoolUtil.SpApiError();
respErr.setName("error");
respErr.setDescr("Failed");
resp.setError(respErr);
}
}

View File

@ -2927,7 +2927,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
throw new InvalidParameterValueException("Volume must be in ready state");
}
if (vol.getPoolId() == storagePoolId) {
if (vol.getPoolId() == storagePoolId.longValue()) {
throw new InvalidParameterValueException("Volume " + vol + " is already on the destination storage pool");
}
@ -2976,9 +2976,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
if (liveMigrateVolume && HypervisorType.KVM.equals(host.getHypervisorType())) {
throw new InvalidParameterValueException("KVM does not support volume live migration due to the limited possibility to refresh VM XML domain. " +
"Therefore, to live migrate a volume between storage pools, one must migrate the VM to a different host as well to force the VM XML domain update. " +
"Use 'migrateVirtualMachineWithVolumes' instead.");
StoragePoolVO destinationStoragePoolVo = _storagePoolDao.findById(storagePoolId);
if (isSourceOrDestNotOnStorPool(storagePoolVO, destinationStoragePoolVo)) {
throw new InvalidParameterValueException("KVM does not support volume live migration due to the limited possibility to refresh VM XML domain. " +
"Therefore, to live migrate a volume between storage pools, one must migrate the VM to a different host as well to force the VM XML domain update. " +
"Use 'migrateVirtualMachineWithVolumes' instead.");
}
}
}
@ -3125,6 +3129,11 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return orchestrateMigrateVolume(vol, destPool, liveMigrateVolume, newDiskOffering);
}
private boolean isSourceOrDestNotOnStorPool(StoragePoolVO storagePoolVO, StoragePoolVO destinationStoragePoolVo) {
return storagePoolVO.getPoolType() != Storage.StoragePoolType.StorPool
|| destinationStoragePoolVo.getPoolType() != Storage.StoragePoolType.StorPool;
}
/**
* Retrieves the new disk offering UUID that might be sent to replace the current one in the volume being migrated.
* If no disk offering UUID is provided we return null. Otherwise, we perform the following checks.

View File

@ -194,9 +194,12 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase):
securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0]
cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id)
cls.clusters = cls.helper.getClustersWithStorPool(cls.apiclient, cls.zone.id,)
cls.vm = VirtualMachine.create(cls.apiclient,
{"name":"StorPool-%s" % uuid.uuid4() },
zoneid=cls.zone.id,
clusterid=random.choice(cls.clusters),
templateid=template.id,
accountid=cls.account.name,
domainid=cls.account.domainid,
@ -207,6 +210,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase):
cls.vm2 = VirtualMachine.create(cls.apiclient,
{"name":"StorPool-%s" % uuid.uuid4() },
zoneid=cls.zone.id,
clusterid=random.choice(cls.clusters),
templateid=template.id,
accountid=cls.account.name,
domainid=cls.account.domainid,
@ -217,6 +221,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase):
cls.vm3 = VirtualMachine.create(cls.apiclient,
{"name":"StorPool-%s" % uuid.uuid4() },
zoneid=cls.zone.id,
clusterid=random.choice(cls.clusters),
templateid=template.id,
accountid=cls.account.name,
domainid=cls.account.domainid,
@ -227,6 +232,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase):
cls.vm4 = VirtualMachine.create(cls.apiclient,
{"name":"StorPool-%s" % uuid.uuid4() },
zoneid=cls.zone.id,
clusterid=random.choice(cls.clusters),
templateid=template.id,
accountid=cls.account.name,
domainid=cls.account.domainid,
@ -237,6 +243,7 @@ class TestMigrateVolumeToAnotherPool(cloudstackTestCase):
cls.vm5 = VirtualMachine.create(cls.apiclient,
{"name":"StorPool-%s" % uuid.uuid4() },
zoneid=cls.zone.id,
clusterid=random.choice(cls.clusters),
templateid=template.id,
accountid=cls.account.name,
domainid=cls.account.domainid,

View File

@ -44,6 +44,7 @@ from marvin.lib.common import (get_zone,
from marvin.cloudstackAPI import (listOsTypes,
listTemplates,
listHosts,
listClusters,
createTemplate,
createVolume,
getVolumeSnapshotDetails,
@ -745,3 +746,33 @@ class StorPoolHelper():
cmd.id = vmid
cmd.hostid = hostid
return (apiclient.startVirtualMachine(cmd))
@classmethod
def getClustersWithStorPool(cls, apiclient, zoneId,):
cmd = listClusters.listClustersCmd()
cmd.zoneid = zoneId
cmd.allocationstate = "Enabled"
clusters = apiclient.listClusters(cmd)
clustersToDeploy = []
for cluster in clusters:
if cluster.resourcedetails['sp.cluster.id']:
clustersToDeploy.append(cluster.id)
return clustersToDeploy
@classmethod
def getHostToDeployOrMigrate(cls, apiclient, hostsToavoid, clustersToDeploy):
hostsOnCluster = []
for c in clustersToDeploy:
hostsOnCluster.append(cls.list_hosts_by_cluster_id(apiclient, c))
destinationHost = None
for host in hostsOnCluster:
if hostsToavoid is None:
return host[0]
if host[0].id not in hostsToavoid:
destinationHost = host[0]
break
return destinationHost

View File

@ -518,7 +518,7 @@ class VirtualMachine:
serviceofferingid=None, securitygroupids=None,
projectid=None, startvm=None, diskofferingid=None,
affinitygroupnames=None, affinitygroupids=None, group=None,
hostid=None, keypair=None, ipaddress=None, mode='default',
hostid=None, clusterid=None, keypair=None, ipaddress=None, mode='default',
method='GET', hypervisor=None, customcpunumber=None,
customcpuspeed=None, custommemory=None, rootdisksize=None,
rootdiskcontroller=None, vpcid=None, macaddress=None, datadisktemplate_diskoffering_list={},
@ -609,6 +609,9 @@ class VirtualMachine:
if hostid:
cmd.hostid = hostid
if clusterid:
cmd.clusterid = clusterid
if "userdata" in services:
cmd.userdata = base64.urlsafe_b64encode(services["userdata"].encode()).decode()