Allow volume migrations in ScaleIO within and across ScaleIO storage clusters (#7408)

* Live storage migration of volume in scaleIO within same storage scaleio cluster

* Added migrate command

* Recent changes of migration across clusters

* Fixed uuid

* recent changes

* Pivot changes

* working blockcopy api in libvirt

* Checking block copy status

* Formatting code

* Fixed failures

* code refactoring and some changes

* Removed unused methods

* removed unused imports

* Unit tests to check if volume belongs to same or different storage scaleio cluster

* Unit tests for volume livemigration in ScaleIOPrimaryDataStoreDriver

* Fixed offline volume migration case and allowed encrypted volume migration

* Added more integration tests

* Support for migration of encrypted volumes across different scaleio clusters

* Fix UI notifications for migrate volume

* Data volume offline migration: save encryption details to destination volume entry

* Offline storage migration for scaleio encrypted volumes

* Allow multiple Volumes to be migrated with migrateVirtualMachineWithVolume API

* Removed unused unittests

* Removed duplicate keys in migrate volume vue file

* Fix Unit tests

* Add volume secrets if does not exists during volume migrations. secrets are getting cleared on package upgrades.

* Fix secret UUID for encrypted volume migration

* Added a null check for secret before removing

* Added more unit tests

* Fixed passphrase check

* Add image options to the encypted volume conversion
This commit is contained in:
Harikrishna 2023-06-21 11:57:05 +05:30 committed by GitHub
parent 3748f32bc7
commit 40cc10a73d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1722 additions and 39 deletions

View File

@ -212,6 +212,7 @@ import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager; import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateVO;
@ -2925,7 +2926,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
* </ul> * </ul>
*/ */
protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) { protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) {
if (!currentPool.isManaged()) { if (!currentPool.isManaged() || currentPool.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
return; return;
} }
if (currentPool.getId() == targetPool.getId()) { if (currentPool.getId() == targetPool.getId()) {

View File

@ -32,6 +32,8 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
@ -46,6 +48,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.stubbing.Answer;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import com.cloud.agent.AgentManager; import com.cloud.agent.AgentManager;
@ -68,6 +71,7 @@ import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager; import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StoragePoolHostVO;
@ -373,9 +377,26 @@ public class VirtualMachineManagerImplTest {
Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId(); Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId();
} }
@Test
public void allowVolumeMigrationsForPowerFlexStorage() {
Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
Mockito.doReturn(Storage.StoragePoolType.PowerFlex).when(storagePoolVoMock).getPoolType();
virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class));
Mockito.verify(storagePoolVoMock).isManaged();
Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId();
}
@Test @Test
public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolEqualsTargetPool() { public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolEqualsTargetPool() {
Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
// return any storage type except powerflex/scaleio
List<Storage.StoragePoolType> values = Arrays.asList(Storage.StoragePoolType.values());
when(storagePoolVoMock.getPoolType()).thenAnswer((Answer<Storage.StoragePoolType>) invocation -> {
List<Storage.StoragePoolType> filteredValues = values.stream().filter(v -> v != Storage.StoragePoolType.PowerFlex).collect(Collectors.toList());
int randomIndex = new Random().nextInt(filteredValues.size());
return filteredValues.get(randomIndex); });
virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, storagePoolVoMock); virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, storagePoolVoMock);
@ -386,6 +407,12 @@ public class VirtualMachineManagerImplTest {
@Test(expected = CloudRuntimeException.class) @Test(expected = CloudRuntimeException.class)
public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolNotEqualsTargetPool() { public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolNotEqualsTargetPool() {
Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); Mockito.doReturn(true).when(storagePoolVoMock).isManaged();
// return any storage type except powerflex/scaleio
List<Storage.StoragePoolType> values = Arrays.asList(Storage.StoragePoolType.values());
when(storagePoolVoMock.getPoolType()).thenAnswer((Answer<Storage.StoragePoolType>) invocation -> {
List<Storage.StoragePoolType> filteredValues = values.stream().filter(v -> v != Storage.StoragePoolType.PowerFlex).collect(Collectors.toList());
int randomIndex = new Random().nextInt(filteredValues.size());
return filteredValues.get(randomIndex); });
virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class));
} }

View File

@ -1648,6 +1648,10 @@ public class VolumeServiceImpl implements VolumeService {
newVol.setPoolType(pool.getPoolType()); newVol.setPoolType(pool.getPoolType());
newVol.setLastPoolId(lastPoolId); newVol.setLastPoolId(lastPoolId);
newVol.setPodId(pool.getPodId()); newVol.setPodId(pool.getPodId());
if (volume.getPassphraseId() != null) {
newVol.setPassphraseId(volume.getPassphraseId());
newVol.setEncryptFormat(volume.getEncryptFormat());
}
return volDao.persist(newVol); return volDao.persist(newVol);
} }

View File

@ -21,6 +21,7 @@ import java.util.Map;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.libvirt.Connect; import org.libvirt.Connect;
import org.libvirt.Library;
import org.libvirt.LibvirtException; import org.libvirt.LibvirtException;
import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor;
@ -44,6 +45,7 @@ public class LibvirtConnection {
if (conn == null) { if (conn == null) {
s_logger.info("No existing libvirtd connection found. Opening a new one"); s_logger.info("No existing libvirtd connection found. Opening a new one");
conn = new Connect(hypervisorURI, false); conn = new Connect(hypervisorURI, false);
Library.initEventLoop();
s_logger.debug("Successfully connected to libvirt at: " + hypervisorURI); s_logger.debug("Successfully connected to libvirt at: " + hypervisorURI);
s_connections.put(hypervisorURI, conn); s_connections.put(hypervisorURI, conn);
} else { } else {

View File

@ -29,27 +29,255 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper; import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper; import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainBlockJobInfo;
import org.libvirt.DomainInfo;
import org.libvirt.TypedParameter;
import org.libvirt.TypedUlongParameter;
import org.libvirt.LibvirtException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
@ResourceWrapper(handles = MigrateVolumeCommand.class) @ResourceWrapper(handles = MigrateVolumeCommand.class)
public final class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVolumeCommand, Answer, LibvirtComputingResource> { public class LibvirtMigrateVolumeCommandWrapper extends CommandWrapper<MigrateVolumeCommand, Answer, LibvirtComputingResource> {
private static final Logger LOGGER = Logger.getLogger(LibvirtMigrateVolumeCommandWrapper.class); private static final Logger LOGGER = Logger.getLogger(LibvirtMigrateVolumeCommandWrapper.class);
@Override @Override
public Answer execute(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { public Answer execute(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData();
PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore();
MigrateVolumeAnswer answer;
if (srcPrimaryDataStore.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
answer = migratePowerFlexVolume(command, libvirtComputingResource);
} else {
answer = migrateRegularVolume(command, libvirtComputingResource);
}
return answer;
}
protected MigrateVolumeAnswer migratePowerFlexVolume(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
// Source Details
VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData();
String srcPath = srcVolumeObjectTO.getPath();
final String srcVolumeId = ScaleIOUtil.getVolumePath(srcVolumeObjectTO.getPath());
final String vmName = srcVolumeObjectTO.getVmName();
// Destination Details
VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData();
String destPath = destVolumeObjectTO.getPath();
final String destVolumeId = ScaleIOUtil.getVolumePath(destVolumeObjectTO.getPath());
Map<String, String> destDetails = command.getDestDetails();
final String destSystemId = destDetails.get(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
String destDiskLabel = null;
final String destDiskFileName = ScaleIOUtil.DISK_NAME_PREFIX + destSystemId + "-" + destVolumeId;
final String diskFilePath = ScaleIOUtil.DISK_PATH + File.separator + destDiskFileName;
Domain dm = null;
try {
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
Connect conn = libvirtUtilitiesHelper.getConnection();
dm = libvirtComputingResource.getDomain(conn, vmName);
if (dm == null) {
return new MigrateVolumeAnswer(command, false, "Migrate volume failed due to can not find vm: " + vmName, null);
}
DomainInfo.DomainState domainState = dm.getInfo().state ;
if (domainState != DomainInfo.DomainState.VIR_DOMAIN_RUNNING) {
return new MigrateVolumeAnswer(command, false, "Migrate volume failed due to VM is not running: " + vmName + " with domainState = " + domainState, null);
}
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
PrimaryDataStoreTO spool = (PrimaryDataStoreTO)destVolumeObjectTO.getDataStore();
KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getPoolType(), spool.getUuid());
pool.connectPhysicalDisk(destVolumeObjectTO.getPath(), null);
String srcSecretUUID = null;
String destSecretUUID = null;
if (ArrayUtils.isNotEmpty(destVolumeObjectTO.getPassphrase())) {
srcSecretUUID = libvirtComputingResource.createLibvirtVolumeSecret(conn, srcVolumeObjectTO.getPath(), srcVolumeObjectTO.getPassphrase());
destSecretUUID = libvirtComputingResource.createLibvirtVolumeSecret(conn, destVolumeObjectTO.getPath(), destVolumeObjectTO.getPassphrase());
}
String diskdef = generateDestinationDiskXML(dm, srcVolumeId, diskFilePath, destSecretUUID);
destDiskLabel = generateDestinationDiskLabel(diskdef);
TypedUlongParameter parameter = new TypedUlongParameter("bandwidth", 0);
TypedParameter[] parameters = new TypedParameter[1];
parameters[0] = parameter;
dm.blockCopy(destDiskLabel, diskdef, parameters, Domain.BlockCopyFlags.REUSE_EXT);
LOGGER.info(String.format("Block copy has started for the volume %s : %s ", destDiskLabel, srcPath));
return checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, srcSecretUUID);
} catch (Exception e) {
String msg = "Migrate volume failed due to " + e.toString();
LOGGER.warn(msg, e);
if (destDiskLabel != null) {
try {
dm.blockJobAbort(destDiskLabel, Domain.BlockJobAbortFlags.ASYNC);
} catch (LibvirtException ex) {
LOGGER.error("Migrate volume failed while aborting the block job due to " + ex.getMessage());
}
}
return new MigrateVolumeAnswer(command, false, msg, null);
} finally {
if (dm != null) {
try {
dm.free();
} catch (LibvirtException l) {
LOGGER.trace("Ignoring libvirt error.", l);
};
}
}
}
protected MigrateVolumeAnswer checkBlockJobStatus(MigrateVolumeCommand command, Domain dm, String diskLabel, String srcPath, String destPath, LibvirtComputingResource libvirtComputingResource, Connect conn, String srcSecretUUID) throws LibvirtException {
int timeBetweenTries = 1000; // Try more frequently (every sec) and return early if disk is found
int waitTimeInSec = command.getWait();
while (waitTimeInSec > 0) {
DomainBlockJobInfo blockJobInfo = dm.getBlockJobInfo(diskLabel, 0);
if (blockJobInfo != null) {
LOGGER.debug(String.format("Volume %s : %s block copy progress: %s%% current value:%s end value:%s", diskLabel, srcPath, (blockJobInfo.end == 0)? 0 : 100*(blockJobInfo.cur / (double) blockJobInfo.end), blockJobInfo.cur, blockJobInfo.end));
if (blockJobInfo.cur == blockJobInfo.end) {
LOGGER.info(String.format("Block copy completed for the volume %s : %s", diskLabel, srcPath));
dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.PIVOT);
if (StringUtils.isNotEmpty(srcSecretUUID)) {
libvirtComputingResource.removeLibvirtVolumeSecret(conn, srcSecretUUID);
}
break;
}
} else {
LOGGER.info("Failed to get the block copy status, trying to abort the job");
dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.ASYNC);
}
waitTimeInSec--;
try {
Thread.sleep(timeBetweenTries);
} catch (Exception ex) {
// don't do anything
}
}
if (waitTimeInSec <= 0) {
String msg = "Block copy is taking long time, failing the job";
LOGGER.error(msg);
try {
dm.blockJobAbort(diskLabel, Domain.BlockJobAbortFlags.ASYNC);
} catch (LibvirtException ex) {
LOGGER.error("Migrate volume failed while aborting the block job due to " + ex.getMessage());
}
return new MigrateVolumeAnswer(command, false, msg, null);
}
return new MigrateVolumeAnswer(command, true, null, destPath);
}
private String generateDestinationDiskLabel(String diskXml) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new ByteArrayInputStream(diskXml.getBytes("UTF-8")));
doc.getDocumentElement().normalize();
Element disk = doc.getDocumentElement();
String diskLabel = getAttrValue("target", "dev", disk);
return diskLabel;
}
protected String generateDestinationDiskXML(Domain dm, String srcVolumeId, String diskFilePath, String destSecretUUID) throws LibvirtException, ParserConfigurationException, IOException, TransformerException, SAXException {
final String domXml = dm.getXMLDesc(0);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new ByteArrayInputStream(domXml.getBytes("UTF-8")));
doc.getDocumentElement().normalize();
NodeList disks = doc.getElementsByTagName("disk");
for (int i = 0; i < disks.getLength(); i++) {
Element disk = (Element)disks.item(i);
String type = disk.getAttribute("type");
if (!type.equalsIgnoreCase("network")) {
String diskDev = getAttrValue("source", "dev", disk);
if (StringUtils.isNotEmpty(diskDev) && diskDev.contains(srcVolumeId)) {
setAttrValue("source", "dev", diskFilePath, disk);
if (StringUtils.isNotEmpty(destSecretUUID)) {
setAttrValue("secret", "uuid", destSecretUUID, disk);
}
StringWriter diskSection = new StringWriter();
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
xformer.transform(new DOMSource(disk), new StreamResult(diskSection));
return diskSection.toString();
}
}
}
return null;
}
private static String getAttrValue(String tag, String attr, Element eElement) {
NodeList tagNode = eElement.getElementsByTagName(tag);
if (tagNode.getLength() == 0) {
return null;
}
Element node = (Element)tagNode.item(0);
return node.getAttribute(attr);
}
private static void setAttrValue(String tag, String attr, String newValue, Element eElement) {
NodeList tagNode = eElement.getElementsByTagName(tag);
if (tagNode.getLength() == 0) {
return;
}
Element node = (Element)tagNode.item(0);
node.setAttribute(attr, newValue);
}
protected MigrateVolumeAnswer migrateRegularVolume(final MigrateVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {
KVMStoragePoolManager storagePoolManager = libvirtComputingResource.getStoragePoolMgr(); KVMStoragePoolManager storagePoolManager = libvirtComputingResource.getStoragePoolMgr();
VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData(); VolumeObjectTO srcVolumeObjectTO = (VolumeObjectTO)command.getSrcData();
PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore(); PrimaryDataStoreTO srcPrimaryDataStore = (PrimaryDataStoreTO)srcVolumeObjectTO.getDataStore();
Map<String, String> srcDetails = command.getSrcDetails(); Map<String, String> srcDetails = command.getSrcDetails();
String srcPath = srcDetails != null ? srcDetails.get(DiskTO.IQN) : srcVolumeObjectTO.getPath(); String srcPath = srcDetails != null ? srcDetails.get(DiskTO.IQN) : srcVolumeObjectTO.getPath();
VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData(); VolumeObjectTO destVolumeObjectTO = (VolumeObjectTO)command.getDestData();

View File

@ -37,6 +37,7 @@ import java.util.UUID;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Volume;
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer; import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.HttpDirectDownloadCommand;
@ -2448,7 +2449,12 @@ public class KVMStorageProcessor implements StorageProcessor {
destPool = storagePoolMgr.getStoragePool(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid()); destPool = storagePoolMgr.getStoragePool(destPrimaryStore.getPoolType(), destPrimaryStore.getUuid());
try { try {
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds()); if (srcVol.getPassphrase() != null && srcVol.getVolumeType().equals(Volume.Type.ROOT)) {
volume.setQemuEncryptFormat(QemuObject.EncryptFormat.LUKS);
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds(), srcVol.getPassphrase(), destVol.getPassphrase(), srcVol.getProvisioningType());
} else {
storagePoolMgr.copyPhysicalDisk(volume, destVolumeName, destPool, cmd.getWaitInMillSeconds());
}
} catch (Exception e) { // Any exceptions while copying the disk, should send failed answer with the error message } catch (Exception e) { // Any exceptions while copying the disk, should send failed answer with the error message
String errMsg = String.format("Failed to copy volume: %s to dest storage: %s, due to %s", srcVol.getName(), destPrimaryStore.getName(), e.toString()); String errMsg = String.format("Failed to copy volume: %s to dest storage: %s, due to %s", srcVol.getName(), destPrimaryStore.getName(), e.toString());
s_logger.debug(errMsg, e); s_logger.debug(errMsg, e);
@ -2467,6 +2473,7 @@ public class KVMStorageProcessor implements StorageProcessor {
String path = destPrimaryStore.isManaged() ? destVolumeName : destVolumePath + File.separator + destVolumeName; String path = destPrimaryStore.isManaged() ? destVolumeName : destVolumePath + File.separator + destVolumeName;
newVol.setPath(path); newVol.setPath(path);
newVol.setFormat(destFormat); newVol.setFormat(destFormat);
newVol.setEncryptFormat(destVol.getEncryptFormat());
return new CopyCmdAnswer(newVol); return new CopyCmdAnswer(newVol);
} catch (final CloudRuntimeException e) { } catch (final CloudRuntimeException e) {
s_logger.debug("Failed to copyVolumeFromPrimaryToPrimary: ", e); s_logger.debug("Failed to copyVolumeFromPrimaryToPrimary: ", e);

View File

@ -387,6 +387,7 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
boolean forceSourceFormat = srcQemuFile.getFormat() == QemuImg.PhysicalDiskFormat.RAW; 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)); 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));
qemuImageOpts.setImageOptsFlag(true);
qemu.convert(srcQemuFile, destQemuFile, options, qemuObjects, qemuImageOpts,null, forceSourceFormat); qemu.convert(srcQemuFile, destQemuFile, options, qemuObjects, qemuImageOpts,null, forceSourceFormat);
LOGGER.debug("Successfully converted source disk image " + srcQemuFile.getFileName() + " to PowerFlex volume: " + destDisk.getPath()); LOGGER.debug("Successfully converted source disk image " + srcQemuFile.getFileName() + " to PowerFlex volume: " + destDisk.getPath());

View File

@ -30,6 +30,7 @@ public class QemuImageOptions {
private static final String LUKS_KEY_SECRET_PARAM_KEY = "key-secret"; private static final String LUKS_KEY_SECRET_PARAM_KEY = "key-secret";
private static final String QCOW2_KEY_SECRET_PARAM_KEY = "encrypt.key-secret"; private static final String QCOW2_KEY_SECRET_PARAM_KEY = "encrypt.key-secret";
private static final String DRIVER = "driver"; private static final String DRIVER = "driver";
private boolean addImageOpts = false;
private QemuImg.PhysicalDiskFormat format; private QemuImg.PhysicalDiskFormat format;
private static final List<QemuImg.PhysicalDiskFormat> DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS = Arrays.asList(QemuImg.PhysicalDiskFormat.QCOW2, QemuImg.PhysicalDiskFormat.LUKS); private static final List<QemuImg.PhysicalDiskFormat> DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS = Arrays.asList(QemuImg.PhysicalDiskFormat.QCOW2, QemuImg.PhysicalDiskFormat.LUKS);
@ -71,13 +72,19 @@ public class QemuImageOptions {
} }
} }
public void setImageOptsFlag(boolean addImageOpts) {
this.addImageOpts = addImageOpts;
}
/** /**
* Converts QemuImageOptions into the command strings required by qemu-img flags * Converts QemuImageOptions into the command strings required by qemu-img flags
* @return array of strings representing command flag and value (--image-opts) * @return array of strings representing command flag and value (--image-opts)
*/ */
public String[] toCommandFlag() { public String[] toCommandFlag() {
if (format == null || !DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS.contains(format)) { if (!addImageOpts) {
return new String[] { params.get(FILENAME_PARAM_KEY) }; if (format == null || !DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS.contains(format)) {
return new String[] { params.get(FILENAME_PARAM_KEY) };
}
} }
Map<String, String> sorted = new TreeMap<>(params); Map<String, String> sorted = new TreeMap<>(params);
String paramString = Joiner.on(",").withKeyValueSeparator("=").join(sorted); String paramString = Joiner.on(",").withKeyValueSeparator("=").join(sorted);

View File

@ -0,0 +1,388 @@
//
// 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.resource.wrapper;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
import com.cloud.agent.api.storage.MigrateVolumeCommand;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.storage.Storage;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainBlockJobInfo;
import org.libvirt.DomainInfo;
import org.libvirt.LibvirtException;
import org.libvirt.TypedParameter;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RunWith(MockitoJUnitRunner.class)
public class LibvirtMigrateVolumeCommandWrapperTest {
@Spy
@InjectMocks
private LibvirtMigrateVolumeCommandWrapper libvirtMigrateVolumeCommandWrapper;
@Mock
MigrateVolumeCommand command;
@Mock
LibvirtComputingResource libvirtComputingResource;
@Mock
LibvirtUtilitiesHelper libvirtUtilitiesHelper;
private String domxml = "<domain type='kvm' id='1'>\n" +
" <name>i-2-27-VM</name>\n" +
" <uuid>2d37fe1a-621a-4903-9ab5-5c9544c733f8</uuid>\n" +
" <description>Ubuntu 18.04 LTS</description>\n" +
" <memory unit='KiB'>524288</memory>\n" +
" <currentMemory unit='KiB'>524288</currentMemory>\n" +
" <vcpu placement='static'>1</vcpu>\n" +
" <cputune>\n" +
" <shares>256</shares>\n" +
" </cputune>\n" +
" <resource>\n" +
" <partition>/machine</partition>\n" +
" </resource>\n" +
" <sysinfo type='smbios'>\n" +
" <system>\n" +
" <entry name='manufacturer'>Apache Software Foundation</entry>\n" +
" <entry name='product'>CloudStack KVM Hypervisor</entry>\n" +
" <entry name='uuid'>2d37fe1a-621a-4903-9ab5-5c9544c733f8</entry>\n" +
" </system>\n" +
" </sysinfo>\n" +
" <os>\n" +
" <type arch='x86_64' machine='pc-i440fx-rhel7.6.0'>hvm</type>\n" +
" <boot dev='cdrom'/>\n" +
" <boot dev='hd'/>\n" +
" <smbios mode='sysinfo'/>\n" +
" </os>\n" +
" <features>\n" +
" <acpi/>\n" +
" <apic/>\n" +
" <pae/>\n" +
" </features>\n" +
" <cpu mode='custom' match='exact' check='full'>\n" +
" <model fallback='forbid'>qemu64</model>\n" +
" <feature policy='require' name='x2apic'/>\n" +
" <feature policy='require' name='hypervisor'/>\n" +
" <feature policy='require' name='lahf_lm'/>\n" +
" <feature policy='disable' name='svm'/>\n" +
" </cpu>\n" +
" <clock offset='utc'>\n" +
" <timer name='kvmclock'/>\n" +
" </clock>\n" +
" <on_poweroff>destroy</on_poweroff>\n" +
" <on_reboot>restart</on_reboot>\n" +
" <on_crash>destroy</on_crash>\n" +
" <devices>\n" +
" <emulator>/usr/libexec/qemu-kvm</emulator>\n" +
" <disk type='block' device='disk'>\n" +
" <driver name='qemu' type='raw' cache='none'/>\n" +
" <source dev='/dev/disk/by-id/emc-vol-610204d03e3ad60f-bec108c400000018' index='4'/>\n" +
" <backingStore/>\n" +
" <target dev='vda' bus='virtio'/>\n" +
" <serial>38a54bf719f24af6b070</serial>\n" +
" <alias name='virtio-disk0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>\n" +
" </disk>\n" +
" <disk type='block' device='disk'>\n" +
" <driver name='qemu' type='raw' cache='none'/>\n" +
" <source dev='/dev/disk/by-id/emc-vol-7332760565f6340f-01b381820000001c' index='2'/>\n" +
" <backingStore/>\n" +
" <target dev='vdb' bus='virtio'/>\n" +
" <serial>0ceeb7c643b447aba5ce</serial>\n" +
" <alias name='virtio-disk1'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>\n" +
" </disk>\n" +
" <disk type='file' device='cdrom'>\n" +
" <driver name='qemu'/>\n" +
" <target dev='hdc' bus='ide'/>\n" +
" <readonly/>\n" +
" <alias name='ide0-1-0'/>\n" +
" <address type='drive' controller='0' bus='1' target='0' unit='0'/>\n" +
" </disk>\n" +
" <controller type='usb' index='0' model='piix3-uhci'>\n" +
" <alias name='usb'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>\n" +
" </controller>\n" +
" <controller type='pci' index='0' model='pci-root'>\n" +
" <alias name='pci.0'/>\n" +
" </controller>\n" +
" <controller type='ide' index='0'>\n" +
" <alias name='ide'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>\n" +
" </controller>\n" +
" <controller type='virtio-serial' index='0'>\n" +
" <alias name='virtio-serial0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>\n" +
" </controller>\n" +
" <interface type='bridge'>\n" +
" <mac address='02:00:23:fd:00:17'/>\n" +
" <source bridge='breth1-1640'/>\n" +
" <bandwidth>\n" +
" <inbound average='25600' peak='25600'/>\n" +
" <outbound average='25600' peak='25600'/>\n" +
" </bandwidth>\n" +
" <target dev='vnet0'/>\n" +
" <model type='virtio'/>\n" +
" <link state='up'/>\n" +
" <alias name='net0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>\n" +
" </interface>\n" +
" <serial type='pty'>\n" +
" <source path='/dev/pts/1'/>\n" +
" <target type='isa-serial' port='0'>\n" +
" <model name='isa-serial'/>\n" +
" </target>\n" +
" <alias name='serial0'/>\n" +
" </serial>\n" +
" <console type='pty' tty='/dev/pts/1'>\n" +
" <source path='/dev/pts/1'/>\n" +
" <target type='serial' port='0'/>\n" +
" <alias name='serial0'/>\n" +
" </console>\n" +
" <channel type='unix'>\n" +
" <source mode='bind' path='/var/lib/libvirt/qemu/i-2-27-VM.org.qemu.guest_agent.0'/>\n" +
" <target type='virtio' name='org.qemu.guest_agent.0' state='connected'/>\n" +
" <alias name='channel0'/>\n" +
" <address type='virtio-serial' controller='0' bus='0' port='1'/>\n" +
" </channel>\n" +
" <input type='tablet' bus='usb'>\n" +
" <alias name='input0'/>\n" +
" <address type='usb' bus='0' port='1'/>\n" +
" </input>\n" +
" <input type='mouse' bus='ps2'>\n" +
" <alias name='input1'/>\n" +
" </input>\n" +
" <input type='keyboard' bus='ps2'>\n" +
" <alias name='input2'/>\n" +
" </input>\n" +
" <graphics type='vnc' port='5900' autoport='yes' listen='10.0.32.170'>\n" +
" <listen type='address' address='10.0.32.170'/>\n" +
" </graphics>\n" +
" <audio id='1' type='none'/>\n" +
" <video>\n" +
" <model type='cirrus' vram='16384' heads='1' primary='yes'/>\n" +
" <alias name='video0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>\n" +
" </video>\n" +
" <watchdog model='i6300esb' action='none'>\n" +
" <alias name='watchdog0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>\n" +
" </watchdog>\n" +
" <memballoon model='virtio'>\n" +
" <alias name='balloon0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>\n" +
" </memballoon>\n" +
" </devices>\n" +
" <seclabel type='dynamic' model='dac' relabel='yes'>\n" +
" <label>+0:+0</label>\n" +
" <imagelabel>+0:+0</imagelabel>\n" +
" </seclabel>\n" +
"</domain>\n";
@Test
public void testPowerFlexMigrateVolumeMethod() {
VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
PrimaryDataStoreTO srcPrimaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
Mockito.doReturn(srcPrimaryDataStore).when(srcVolumeObjectTO).getDataStore();
Mockito.doReturn(Storage.StoragePoolType.PowerFlex).when(srcPrimaryDataStore).getPoolType();
MigrateVolumeAnswer powerFlexAnswer = Mockito.mock(MigrateVolumeAnswer.class);
MigrateVolumeAnswer regularVolumeAnswer = Mockito.mock(MigrateVolumeAnswer.class);
Mockito.doReturn(true).when(powerFlexAnswer).getResult();
Mockito.doReturn(powerFlexAnswer).when(libvirtMigrateVolumeCommandWrapper).migratePowerFlexVolume(command, libvirtComputingResource);
Answer answer = libvirtMigrateVolumeCommandWrapper.execute(command, libvirtComputingResource);
Assert.assertTrue(answer.getResult());
}
@Test
public void testRegularMigrateVolumeMethod() {
VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
PrimaryDataStoreTO srcPrimaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
Mockito.doReturn(srcPrimaryDataStore).when(srcVolumeObjectTO).getDataStore();
Mockito.doReturn(Storage.StoragePoolType.NetworkFilesystem).when(srcPrimaryDataStore).getPoolType();
MigrateVolumeAnswer powerFlexAnswer = Mockito.mock(MigrateVolumeAnswer.class);
MigrateVolumeAnswer regularVolumeAnswer = Mockito.mock(MigrateVolumeAnswer.class);
Mockito.doReturn(false).when(regularVolumeAnswer).getResult();
Mockito.doReturn(regularVolumeAnswer).when(libvirtMigrateVolumeCommandWrapper).migrateRegularVolume(command, libvirtComputingResource);
Answer answer = libvirtMigrateVolumeCommandWrapper.execute(command, libvirtComputingResource);
Assert.assertFalse(answer.getResult());
}
@Test
public void testMigratePowerFlexVolume() throws LibvirtException, ParserConfigurationException, IOException, TransformerException, SAXException {
VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
String srcPath = "bec108c400000018:vol-60-7acb-9e22";
Mockito.doReturn(srcPath).when(srcVolumeObjectTO).getPath();
String vmName = "i-2-27-VM";
Mockito.doReturn(vmName).when(srcVolumeObjectTO).getVmName();
VolumeObjectTO destVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
Mockito.doReturn(destVolumeObjectTO).when(command).getDestData();
String destPath = "01b381820000001c:vol-60-ec76-b7dc";
Mockito.doReturn(destPath).when(destVolumeObjectTO).getPath();
Map<String, String> destDetails = new HashMap<>();
destDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, "610204d03e3ad60f");
Mockito.doReturn(libvirtUtilitiesHelper).when(libvirtComputingResource).getLibvirtUtilitiesHelper();
Connect conn = Mockito.mock(Connect.class);
Domain dm = Mockito.mock(Domain.class);
Mockito.doReturn(conn).when(libvirtUtilitiesHelper).getConnection();
Mockito.doReturn(dm).when(libvirtComputingResource).getDomain(conn, vmName);
DomainInfo domainInfo = Mockito.mock(DomainInfo.class);
domainInfo.state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING;
Mockito.doReturn(domainInfo).when(dm).getInfo();
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
Mockito.doReturn(storagePoolMgr).when(libvirtComputingResource).getStoragePoolMgr();
PrimaryDataStoreTO spool = Mockito.mock(PrimaryDataStoreTO.class);
Mockito.doReturn(spool).when(destVolumeObjectTO).getDataStore();
KVMStoragePool pool = Mockito.mock(KVMStoragePool.class);
Mockito.doReturn(pool).when(storagePoolMgr).getStoragePool(Mockito.any(), Mockito.any());
Mockito.doReturn(true).when(pool).connectPhysicalDisk(Mockito.any(), Mockito.any());
Mockito.doReturn(null).when(destVolumeObjectTO).getPassphrase();
Mockito.doReturn(domxml).when(dm).getXMLDesc(0);
Mockito.doNothing().when(dm).blockCopy(Matchers.anyString(), Matchers.anyString(), Matchers.any(TypedParameter[].class), Matchers.anyInt());
MigrateVolumeAnswer answer = new MigrateVolumeAnswer(command, true, null, destPath);
Mockito.doReturn(answer).when(libvirtMigrateVolumeCommandWrapper).checkBlockJobStatus(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Answer migrateVolumeAnswer = libvirtMigrateVolumeCommandWrapper.migratePowerFlexVolume(command, libvirtComputingResource);
Assert.assertTrue(migrateVolumeAnswer.getResult());
}
@Test
public void testMigratePowerFlexVolumeFailure() throws LibvirtException, ParserConfigurationException, IOException, TransformerException, SAXException {
VolumeObjectTO srcVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
Mockito.doReturn(srcVolumeObjectTO).when(command).getSrcData();
String srcPath = "bec108c400000018:vol-60-7acb-9e22";
Mockito.doReturn(srcPath).when(srcVolumeObjectTO).getPath();
String vmName = "i-2-27-VM";
Mockito.doReturn(vmName).when(srcVolumeObjectTO).getVmName();
VolumeObjectTO destVolumeObjectTO = Mockito.mock(VolumeObjectTO.class);
Mockito.doReturn(destVolumeObjectTO).when(command).getDestData();
String destPath = "01b381820000001c:vol-60-ec76-b7dc";
Mockito.doReturn(destPath).when(destVolumeObjectTO).getPath();
Map<String, String> destDetails = new HashMap<>();
destDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, "610204d03e3ad60f");
Mockito.doReturn(libvirtUtilitiesHelper).when(libvirtComputingResource).getLibvirtUtilitiesHelper();
Connect conn = Mockito.mock(Connect.class);
Domain dm = Mockito.mock(Domain.class);
Mockito.doReturn(conn).when(libvirtUtilitiesHelper).getConnection();
Mockito.doReturn(dm).when(libvirtComputingResource).getDomain(conn, vmName);
DomainInfo domainInfo = Mockito.mock(DomainInfo.class);
domainInfo.state = DomainInfo.DomainState.VIR_DOMAIN_RUNNING;
Mockito.doReturn(domainInfo).when(dm).getInfo();
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
Mockito.doReturn(storagePoolMgr).when(libvirtComputingResource).getStoragePoolMgr();
PrimaryDataStoreTO spool = Mockito.mock(PrimaryDataStoreTO.class);
Mockito.doReturn(spool).when(destVolumeObjectTO).getDataStore();
KVMStoragePool pool = Mockito.mock(KVMStoragePool.class);
Mockito.doReturn(pool).when(storagePoolMgr).getStoragePool(Mockito.any(), Mockito.any());
Mockito.doReturn(true).when(pool).connectPhysicalDisk(Mockito.any(), Mockito.any());
Mockito.doReturn(null).when(destVolumeObjectTO).getPassphrase();
Mockito.doReturn(domxml).when(dm).getXMLDesc(0);
Mockito.doThrow(LibvirtException.class).when(dm).blockCopy(Matchers.anyString(), Matchers.anyString(), Matchers.any(TypedParameter[].class), Matchers.anyInt());
Answer migrateVolumeAnswer = libvirtMigrateVolumeCommandWrapper.migratePowerFlexVolume(command, libvirtComputingResource);
Assert.assertFalse(migrateVolumeAnswer.getResult());
}
@Test
public void testCheckBlockJobStatus() throws LibvirtException {
Connect conn = Mockito.mock(Connect.class);
Domain dm = Mockito.mock(Domain.class);
String destDiskLabel = "vda";
String srcPath = "bec108c400000018:vol-60-7acb-9e22";
String destPath = "01b381820000001c:vol-60-ec76-b7dc";
Mockito.doReturn(60).when(command).getWait();
DomainBlockJobInfo blockJobInfo = Mockito.mock(DomainBlockJobInfo.class);
Mockito.doReturn(blockJobInfo).when(dm).getBlockJobInfo(destDiskLabel, 0);
blockJobInfo.cur = 100;
blockJobInfo.end = 100;
MigrateVolumeAnswer answer = libvirtMigrateVolumeCommandWrapper.checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, null);
Assert.assertTrue(answer.getResult());
}
@Test
public void testCheckBlockJobStatusFailure() throws LibvirtException {
Connect conn = Mockito.mock(Connect.class);
Domain dm = Mockito.mock(Domain.class);
String destDiskLabel = "vda";
String srcPath = "bec108c400000018:vol-60-7acb-9e22";
String destPath = "01b381820000001c:vol-60-ec76-b7dc";
Mockito.doReturn(1).when(command).getWait();
DomainBlockJobInfo blockJobInfo = Mockito.mock(DomainBlockJobInfo.class);
Mockito.doReturn(blockJobInfo).when(dm).getBlockJobInfo(destDiskLabel, 0);
blockJobInfo.cur = 10;
blockJobInfo.end = 100;
MigrateVolumeAnswer answer = libvirtMigrateVolumeCommandWrapper.checkBlockJobStatus(command, dm, destDiskLabel, srcPath, destPath, libvirtComputingResource, conn, null);
Assert.assertFalse(answer.getResult());
}
}

View File

@ -761,7 +761,7 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient {
} }
String srcPoolId = volume.getStoragePoolId(); String srcPoolId = volume.getStoragePoolId();
LOG.debug("Migrating the volume: " + srcVolumeId + " on the src pool: " + srcPoolId + " to the dest pool: " + destPoolId + LOG.info("Migrating the volume: " + srcVolumeId + " on the src pool: " + srcPoolId + " to the dest pool: " + destPoolId +
" in the same PowerFlex cluster"); " in the same PowerFlex cluster");
post("/instances/Volume::" + srcVolumeId + "/action/migrateVTree", post("/instances/Volume::" + srcVolumeId + "/action/migrateVTree",

View File

@ -22,6 +22,7 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.agent.api.storage.MigrateVolumeCommand;
import com.cloud.agent.api.storage.ResizeVolumeCommand; import com.cloud.agent.api.storage.ResizeVolumeCommand;
import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.host.HostVO; import com.cloud.host.HostVO;
@ -41,6 +42,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.RemoteHostEndPoint; import org.apache.cloudstack.storage.RemoteHostEndPoint;
@ -70,6 +72,7 @@ import com.cloud.agent.api.Answer;
import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO; import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.alert.AlertManager; import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
import com.cloud.host.Host; import com.cloud.host.Host;
@ -126,12 +129,14 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
private HostDao hostDao; private HostDao hostDao;
@Inject @Inject
private VMInstanceDao vmInstanceDao; private VMInstanceDao vmInstanceDao;
@Inject
private VolumeService volumeService;
public ScaleIOPrimaryDataStoreDriver() { public ScaleIOPrimaryDataStoreDriver() {
} }
private ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) throws Exception { public ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) throws Exception {
return ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePoolId, storagePoolDetailsDao); return ScaleIOGatewayClientConnectionPool.getInstance().getClient(storagePoolId, storagePoolDetailsDao);
} }
@ -253,12 +258,33 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
} }
} }
public void revokeVolumeAccess(String volumePath, Host host, DataStore dataStore) {
if (host == null) {
LOGGER.warn("Declining to revoke access to PowerFlex volume when a host is not provided");
return;
}
try {
LOGGER.debug("Revoking access for PowerFlex volume: " + volumePath);
final String sdcId = getConnectedSdc(dataStore.getId(), host.getId());
if (StringUtils.isBlank(sdcId)) {
throw new CloudRuntimeException("Unable to revoke access for volume: " + volumePath + ", no Sdc connected with host ip: " + host.getPrivateIpAddress());
}
final ScaleIOGatewayClient client = getScaleIOClient(dataStore.getId());
client.unmapVolumeFromSdc(ScaleIOUtil.getVolumePath(volumePath), sdcId);
} catch (Exception e) {
LOGGER.warn("Failed to revoke access due to: " + e.getMessage(), e);
}
}
private void revokeAccess(DataObject dataObject, EndPoint ep, DataStore dataStore) { private void revokeAccess(DataObject dataObject, EndPoint ep, DataStore dataStore) {
Host host = hostDao.findById(ep.getId()); Host host = hostDao.findById(ep.getId());
revokeAccess(dataObject, host, dataStore); revokeAccess(dataObject, host, dataStore);
} }
private String getConnectedSdc(long poolId, long hostId) { public String getConnectedSdc(long poolId, long hostId) {
try { try {
StoragePoolHostVO poolHostVO = storagePoolHostDao.findByPoolHost(poolId, hostId); StoragePoolHostVO poolHostVO = storagePoolHostDao.findByPoolHost(poolId, hostId);
if (poolHostVO == null) { if (poolHostVO == null) {
@ -443,7 +469,11 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
} }
} }
private CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long storagePoolId) { public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long storagePoolId) {
return createVolume(volumeInfo, storagePoolId, false);
}
public CreateObjectAnswer createVolume(VolumeInfo volumeInfo, long storagePoolId, boolean migrationInvolved) {
LOGGER.debug("Creating PowerFlex volume"); LOGGER.debug("Creating PowerFlex volume");
StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
@ -474,7 +504,11 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
volume.setFolder(scaleIOVolume.getVtreeId()); volume.setFolder(scaleIOVolume.getVtreeId());
volume.setSize(scaleIOVolume.getSizeInKb() * 1024); volume.setSize(scaleIOVolume.getSizeInKb() * 1024);
volume.setPoolType(Storage.StoragePoolType.PowerFlex); volume.setPoolType(Storage.StoragePoolType.PowerFlex);
volume.setFormat(Storage.ImageFormat.RAW); if (volumeInfo.getVolumeType().equals(Volume.Type.ROOT)) {
volume.setFormat(volumeInfo.getFormat());
} else {
volume.setFormat(Storage.ImageFormat.RAW);
}
volume.setPoolId(storagePoolId); volume.setPoolId(storagePoolId);
VolumeObject createdObject = VolumeObject.getVolumeObject(volumeInfo.getDataStore(), volume); VolumeObject createdObject = VolumeObject.getVolumeObject(volumeInfo.getDataStore(), volume);
createdObject.update(); createdObject.update();
@ -488,7 +522,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
CreateObjectAnswer answer = new CreateObjectAnswer(createdObject.getTO()); CreateObjectAnswer answer = new CreateObjectAnswer(createdObject.getTO());
// 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 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)) { if (anyVolumeRequiresEncryption(volumeInfo) && (!volumeInfo.getVolumeType().equals(Volume.Type.ROOT) || migrationInvolved)) {
LOGGER.debug(String.format("Setting up encryption for volume %s", volumeInfo.getId())); LOGGER.debug(String.format("Setting up encryption for volume %s", volumeInfo.getId()));
VolumeObjectTO prepVolume = (VolumeObjectTO) createdObject.getTO(); VolumeObjectTO prepVolume = (VolumeObjectTO) createdObject.getTO();
prepVolume.setPath(volumePath); prepVolume.setPath(volumePath);
@ -682,7 +716,12 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
if (isSameScaleIOStorageInstance(srcStore, destStore)) { if (isSameScaleIOStorageInstance(srcStore, destStore)) {
answer = migrateVolume(srcData, destData); answer = migrateVolume(srcData, destData);
} else { } else {
answer = copyVolume(srcData, destData, destHost); String vmName = ((VolumeInfo) srcData).getAttachedVmName();
if (vmName == null || !vmInstanceDao.findVMByInstanceName(vmName).getState().equals(VirtualMachine.State.Running)) {
answer = copyOfflineVolume(srcData, destData, destHost);
} else {
answer = liveMigrateVolume(srcData, destData);
}
} }
} else { } else {
errMsg = "Unsupported copy operation from src object: (" + srcData.getType() + ", " + srcData.getDataStore() + "), dest object: (" errMsg = "Unsupported copy operation from src object: (" + srcData.getType() + ", " + srcData.getDataStore() + "), dest object: ("
@ -702,6 +741,9 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
} }
result = new CopyCommandResult(null, answer); result = new CopyCommandResult(null, answer);
if (answer != null && !answer.getResult()) {
result.setResult(answer.getDetails());
}
callback.complete(result); callback.complete(result);
} }
@ -753,7 +795,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
return answer; return answer;
} }
private Answer copyVolume(DataObject srcData, DataObject destData, Host destHost) { protected Answer copyOfflineVolume(DataObject srcData, DataObject destData, Host destHost) {
// Copy PowerFlex/ScaleIO volume // Copy PowerFlex/ScaleIO volume
LOGGER.debug(String.format("Initiating copy from PowerFlex template volume on host %s", destHost != null ? destHost.getId() : "<not specified>")); LOGGER.debug(String.format("Initiating copy from PowerFlex template volume on host %s", destHost != null ? destHost.getId() : "<not specified>"));
String value = configDao.getValue(Config.CopyVolumeWait.key()); String value = configDao.getValue(Config.CopyVolumeWait.key());
@ -775,6 +817,227 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
return answer; return answer;
} }
public Answer liveMigrateVolume(DataObject srcData, DataObject destData) {
// Volume migration across different PowerFlex/ScaleIO clusters
final long srcVolumeId = srcData.getId();
DataStore srcStore = srcData.getDataStore();
Map<String, String> srcDetails = getVolumeDetails((VolumeInfo) srcData, srcStore);
DataStore destStore = destData.getDataStore();
final long destPoolId = destStore.getId();
Map<String, String> destDetails = getVolumeDetails((VolumeInfo) destData, destStore);
VolumeObjectTO destVolTO = (VolumeObjectTO) destData.getTO();
String destVolumePath = null;
Host host = findEndpointForVolumeOperation(srcData);
EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(host);
Answer answer = null;
try {
CreateObjectAnswer createAnswer = createVolume((VolumeInfo) destData, destStore.getId(), true);
destVolumePath = createAnswer.getData().getPath();
destVolTO.setPath(destVolumePath);
grantAccess(destData, host, destData.getDataStore());
int waitInterval = NumbersUtil.parseInt(configDao.getValue(Config.MigrateWait.key()), Integer.parseInt(Config.MigrateWait.getDefaultValue()));
MigrateVolumeCommand migrateVolumeCommand = new MigrateVolumeCommand(srcData.getTO(), destVolTO,
srcDetails, destDetails, waitInterval);
answer = ep.sendMessage(migrateVolumeCommand);
boolean migrateStatus = answer.getResult();
if (migrateStatus) {
updateVolumeAfterCopyVolume(srcData, destData);
updateSnapshotsAfterCopyVolume(srcData, destData);
deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
LOGGER.debug(String.format("Successfully migrated migrate PowerFlex volume %d to storage pool %d", srcVolumeId, destPoolId));
answer = new Answer(null, true, null);
} else {
String errorMsg = "Failed to migrate PowerFlex volume: " + srcVolumeId + " to storage pool " + destPoolId;
LOGGER.debug(errorMsg);
answer = new Answer(null, false, errorMsg);
}
} catch (Exception e) {
LOGGER.error("Failed to migrate PowerFlex volume: " + srcVolumeId + " due to: " + e.getMessage());
answer = new Answer(null, false, e.getMessage());
}
if (destVolumePath != null && !answer.getResult()) {
revertBlockCopyVolumeOperations(srcData, destData, host, destVolumePath);
}
return answer;
}
protected void updateVolumeAfterCopyVolume(DataObject srcData, DataObject destData) {
// destination volume is already created and volume path is set in database by this time at "CreateObjectAnswer createAnswer = createVolume((VolumeInfo) destData, destStore.getId());"
final long srcVolumeId = srcData.getId();
final long destVolumeId = destData.getId();
if (srcVolumeId != destVolumeId) {
VolumeVO srcVolume = volumeDao.findById(srcVolumeId);
srcVolume.set_iScsiName(null);
srcVolume.setPath(null);
srcVolume.setFolder(null);
volumeDao.update(srcVolumeId, srcVolume);
} else {
// Live migrate volume
VolumeVO volume = volumeDao.findById(srcVolumeId);
Long oldPoolId = volume.getPoolId();
volume.setLastPoolId(oldPoolId);
volumeDao.update(srcVolumeId, volume);
}
}
private Host findEndpointForVolumeOperation(DataObject srcData) {
long hostId = 0;
VMInstanceVO instance = vmInstanceDao.findVMByInstanceName(((VolumeInfo) srcData).getAttachedVmName());
if (instance.getState().equals(VirtualMachine.State.Running)) {
hostId = instance.getHostId();
}
if (hostId == 0) {
hostId = selector.select(srcData, true).getId();
}
HostVO host = hostDao.findById(hostId);
if (host == null) {
throw new CloudRuntimeException("Found no hosts to run migrate volume command on");
}
return host;
}
public void updateSnapshotsAfterCopyVolume(DataObject srcData, DataObject destData) throws Exception {
final long srcVolumeId = srcData.getId();
DataStore srcStore = srcData.getDataStore();
final long srcPoolId = srcStore.getId();
final ScaleIOGatewayClient client = getScaleIOClient(srcPoolId);
DataStore destStore = destData.getDataStore();
final long destPoolId = destStore.getId();
final StoragePoolVO destStoragePool = storagePoolDao.findById(destPoolId);
List<SnapshotVO> snapshots = snapshotDao.listByVolumeId(srcVolumeId);
if (CollectionUtils.isNotEmpty(snapshots)) {
for (SnapshotVO snapshot : snapshots) {
SnapshotDataStoreVO snapshotStore = snapshotDataStoreDao.findBySnapshot(snapshot.getId(), DataStoreRole.Primary);
if (snapshotStore == null) {
continue;
}
String snapshotVolumeId = ScaleIOUtil.getVolumePath(snapshotStore.getInstallPath());
String newSnapshotName = String.format("%s-%s-%s-%s", ScaleIOUtil.SNAPSHOT_PREFIX, snapshot.getId(),
destStoragePool.getUuid().split("-")[0].substring(4), ManagementServerImpl.customCsIdentifier.value());
boolean renamed = client.renameVolume(snapshotVolumeId, newSnapshotName);
snapshotStore.setDataStoreId(destPoolId);
// Snapshot Id in the PowerFlex/ScaleIO pool remains the same after the migration
// Update PowerFlex snapshot name only after it is renamed, to maintain the consistency
if (renamed) {
snapshotStore.setInstallPath(ScaleIOUtil.updatedPathWithVolumeName(snapshotVolumeId, newSnapshotName));
}
snapshotDataStoreDao.update(snapshotStore.getId(), snapshotStore);
}
}
}
public void deleteSourceVolumeAfterSuccessfulBlockCopy(DataObject srcData, Host host) {
DataStore srcStore = srcData.getDataStore();
String srcVolumePath = srcData.getTO().getPath();
revokeVolumeAccess(srcVolumePath, host, srcData.getDataStore());
String errMsg;
try {
String scaleIOVolumeId = ScaleIOUtil.getVolumePath(srcVolumePath);
final ScaleIOGatewayClient client = getScaleIOClient(srcStore.getId());
Boolean deleteResult = client.deleteVolume(scaleIOVolumeId);
if (!deleteResult) {
errMsg = "Failed to delete source PowerFlex volume with id: " + scaleIOVolumeId;
LOGGER.warn(errMsg);
}
} catch (Exception e) {
errMsg = "Unable to delete source PowerFlex volume: " + srcVolumePath + " due to " + e.getMessage();
LOGGER.warn(errMsg);;
}
}
public void revertBlockCopyVolumeOperations(DataObject srcData, DataObject destData, Host host, String destVolumePath) {
final String srcVolumePath = ((VolumeInfo) srcData).getPath();
final String srcVolumeFolder = ((VolumeInfo) srcData).getFolder();
DataStore destStore = destData.getDataStore();
revokeAccess(destData, host, destData.getDataStore());
String errMsg;
try {
String scaleIOVolumeId = ScaleIOUtil.getVolumePath(destVolumePath);
final ScaleIOGatewayClient client = getScaleIOClient(destStore.getId());
Boolean deleteResult = client.deleteVolume(scaleIOVolumeId);
if (!deleteResult) {
errMsg = "Failed to delete PowerFlex volume with id: " + scaleIOVolumeId;
LOGGER.warn(errMsg);
}
} catch (Exception e) {
errMsg = "Unable to delete destination PowerFlex volume: " + destVolumePath + " due to " + e.getMessage();
LOGGER.warn(errMsg);
throw new CloudRuntimeException(errMsg, e);
}
final long srcVolumeId = srcData.getId();
if (srcVolumeId == destData.getId()) {
VolumeVO volume = volumeDao.findById(srcVolumeId);
volume.set_iScsiName(srcVolumePath);
volume.setPath(srcVolumePath);
volume.setFolder(srcVolumeFolder);
volume.setPoolId(((VolumeInfo) srcData).getPoolId());
volumeDao.update(srcVolumeId, volume);
}
}
private Map<String, String> getVolumeDetails(VolumeInfo volumeInfo, DataStore dataStore) {
long storagePoolId = dataStore.getId();
StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePoolId);
if (!storagePoolVO.isManaged()) {
return null;
}
Map<String, String> volumeDetails = new HashMap<>();
VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId());
volumeDetails.put(DiskTO.STORAGE_HOST, storagePoolVO.getHostAddress());
volumeDetails.put(DiskTO.STORAGE_PORT, String.valueOf(storagePoolVO.getPort()));
volumeDetails.put(DiskTO.IQN, volumeVO.get_iScsiName());
volumeDetails.put(DiskTO.PROTOCOL_TYPE, (volumeVO.getPoolType() != null) ? volumeVO.getPoolType().toString() : null);
volumeDetails.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(storagePoolVO.getId())));
volumeDetails.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeVO.getSize()));
volumeDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, getVolumeProperty(volumeInfo.getId(), DiskTO.SCSI_NAA_DEVICE_ID));
ChapInfo chapInfo = volumeService.getChapInfo(volumeInfo, dataStore);
if (chapInfo != null) {
volumeDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
volumeDetails.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
volumeDetails.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
volumeDetails.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
}
String systemId = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
volumeDetails.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
return volumeDetails;
}
private String getVolumeProperty(long volumeId, String property) {
VolumeDetailVO volumeDetails = volumeDetailsDao.findDetail(volumeId, property);
if (volumeDetails != null) {
return volumeDetails.getValue();
}
return null;
}
private Answer migrateVolume(DataObject srcData, DataObject destData) { private Answer migrateVolume(DataObject srcData, DataObject destData) {
// Volume migration within same PowerFlex/ScaleIO cluster (with same System ID) // Volume migration within same PowerFlex/ScaleIO cluster (with same System ID)
DataStore srcStore = srcData.getDataStore(); DataStore srcStore = srcData.getDataStore();
@ -861,7 +1124,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
return answer; return answer;
} }
private boolean isSameScaleIOStorageInstance(DataStore srcStore, DataStore destStore) { public boolean isSameScaleIOStorageInstance(DataStore srcStore, DataStore destStore) {
long srcPoolId = srcStore.getId(); long srcPoolId = srcStore.getId();
String srcPoolSystemId = null; String srcPoolSystemId = null;
StoragePoolDetailVO srcPoolSystemIdDetail = storagePoolDetailsDao.findDetail(srcPoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID); StoragePoolDetailVO srcPoolSystemIdDetail = storagePoolDetailsDao.findDetail(srcPoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
@ -1148,7 +1411,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
/** /**
* Does any object require encryption support? * Does any object require encryption support?
*/ */
private boolean anyVolumeRequiresEncryption(DataObject ... objects) { protected boolean anyVolumeRequiresEncryption(DataObject ... objects) {
for (DataObject o : objects) { for (DataObject o : objects) {
if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() != null) { if (o instanceof VolumeInfo && ((VolumeInfo) o).getPassphraseId() != null) {
return true; return true;

View File

@ -0,0 +1,527 @@
//
// 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.Answer;
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
import com.cloud.agent.api.to.DataTO;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.configuration.Config;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
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.engine.subsystem.api.storage.VolumeService;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.cloudstack.storage.command.CreateObjectAnswer;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
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.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest(RemoteHostEndPoint.class)
public class ScaleIOPrimaryDataStoreDriverTest {
@Spy
@InjectMocks
ScaleIOPrimaryDataStoreDriver scaleIOPrimaryDataStoreDriver = new ScaleIOPrimaryDataStoreDriver();
@Mock
StoragePoolDetailsDao storagePoolDetailsDao;
@Mock
PrimaryDataStoreDao storagePoolDao;
@Mock
VolumeDao volumeDao;
@Mock
VolumeDetailsDao volumeDetailsDao;
@Mock
VolumeService volumeService;
@Mock
VMInstanceDao vmInstanceDao;
@Mock
HostDao hostDao;
@Mock
ConfigurationDao configDao;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testSameScaleIOStorageInstance() {
DataStore srcStore = Mockito.mock(DataStore.class);
DataStore destStore = Mockito.mock(DataStore.class);
when(srcStore.getId()).thenReturn(1L);
when(destStore.getId()).thenReturn(2L);
StoragePoolDetailVO srcPoolSystemIdDetail = Mockito.mock(StoragePoolDetailVO.class);
String srcPoolSystemId = "610204d03e3ad60f";
when(srcPoolSystemIdDetail.getValue()).thenReturn(srcPoolSystemId);
StoragePoolDetailVO destPoolSystemIdDetail = Mockito.mock(StoragePoolDetailVO.class);
String destPoolSystemId = "610204d03e3ad60f";
when(destPoolSystemIdDetail.getValue()).thenReturn(destPoolSystemId);
when(storagePoolDetailsDao.findDetail(1L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcPoolSystemIdDetail);
when(storagePoolDetailsDao.findDetail(2L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(destPoolSystemIdDetail);
boolean result = scaleIOPrimaryDataStoreDriver.isSameScaleIOStorageInstance(srcStore, destStore);
Assert.assertTrue(result);
}
@Test
public void testDifferentScaleIOStorageInstance() {
DataStore srcStore = Mockito.mock(DataStore.class);
DataStore destStore = Mockito.mock(DataStore.class);
when(srcStore.getId()).thenReturn(1L);
when(destStore.getId()).thenReturn(2L);
StoragePoolDetailVO srcPoolSystemIdDetail = Mockito.mock(StoragePoolDetailVO.class);
String srcPoolSystemId = "610204d03e3ad60f";
when(srcPoolSystemIdDetail.getValue()).thenReturn(srcPoolSystemId);
StoragePoolDetailVO destPoolSystemIdDetail = Mockito.mock(StoragePoolDetailVO.class);
String destPoolSystemId = "7332760565f6340f";
when(destPoolSystemIdDetail.getValue()).thenReturn(destPoolSystemId);
when(storagePoolDetailsDao.findDetail(1L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcPoolSystemIdDetail);
when(storagePoolDetailsDao.findDetail(2L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(destPoolSystemIdDetail);
boolean result = scaleIOPrimaryDataStoreDriver.isSameScaleIOStorageInstance(srcStore, destStore);
Assert.assertFalse(result);
}
@Test (expected = CloudRuntimeException.class)
public void testCheckVolumeOnDifferentScaleIOStorageInstanceSystemIdShouldNotBeNull() {
DataStore srcStore = Mockito.mock(DataStore.class);
DataStore destStore = Mockito.mock(DataStore.class);
when(srcStore.getId()).thenReturn(1L);
when(destStore.getId()).thenReturn(2L);
StoragePoolDetailVO srcPoolSystemIdDetail = Mockito.mock(StoragePoolDetailVO.class);
String srcPoolSystemId = "610204d03e3ad60f";
when(srcPoolSystemIdDetail.getValue()).thenReturn(srcPoolSystemId);
StoragePoolDetailVO destPoolSystemIdDetail = Mockito.mock(StoragePoolDetailVO.class);
when(destPoolSystemIdDetail.getValue()).thenReturn(null);
when(storagePoolDetailsDao.findDetail(1L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcPoolSystemIdDetail);
when(storagePoolDetailsDao.findDetail(2L,ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(destPoolSystemIdDetail);
scaleIOPrimaryDataStoreDriver.isSameScaleIOStorageInstance(srcStore, destStore);
}
@Test
public void testMigrateVolumeWithinSameScaleIOClusterSuccess() throws Exception {
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
VolumeInfo destData = Mockito.mock(VolumeInfo.class);
DataStore srcStore = Mockito.mock(DataStore.class);
DataStore destStore = Mockito.mock(DataStore.class);
when(srcData.getDataStore()).thenReturn(srcStore);
when(destData.getDataStore()).thenReturn(destStore);
fillSrcVolumeDetails(srcData, srcStore);
fillDestVolumeDetails(destData, destStore);
VolumeObjectTO destVolTO = Mockito.mock(VolumeObjectTO.class);
when(destData.getTO()).thenReturn(destVolTO);
Host host = prepareEndpointForVolumeOperation(srcData);
PowerMockito.mockStatic(RemoteHostEndPoint.class);
RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
when(RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
DataTO dataTO = Mockito.mock(DataTO.class);
CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData, 2L, true);
when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
doReturn(true).when(scaleIOPrimaryDataStoreDriver)
.grantAccess(any(), any(), any());
when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
MigrateVolumeAnswer migrateVolumeAnswer = Mockito.mock(MigrateVolumeAnswer.class);
when(ep.sendMessage(any())).thenReturn(migrateVolumeAnswer);
when(migrateVolumeAnswer.getResult()).thenReturn(true);
Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
.updateVolumeAfterCopyVolume(any(), any());
Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
.updateSnapshotsAfterCopyVolume(any(), any());
Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
.deleteSourceVolumeAfterSuccessfulBlockCopy(any(), any());
Answer answer = scaleIOPrimaryDataStoreDriver.liveMigrateVolume(srcData, destData);
Assert.assertTrue(answer.getResult());
}
@Test
public void testMigrateVolumeWithinSameScaleIOClusterFailure() throws Exception {
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
VolumeInfo destData = Mockito.mock(VolumeInfo.class);
DataStore srcStore = Mockito.mock(DataStore.class);
DataStore destStore = Mockito.mock(DataStore.class);
when(srcData.getDataStore()).thenReturn(srcStore);
when(destData.getDataStore()).thenReturn(destStore);
fillSrcVolumeDetails(srcData, srcStore);
fillDestVolumeDetails(destData, destStore);
VolumeObjectTO destVolTO = Mockito.mock(VolumeObjectTO.class);
when(destData.getTO()).thenReturn(destVolTO);
Host host = prepareEndpointForVolumeOperation(srcData);
PowerMockito.mockStatic(RemoteHostEndPoint.class);
RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
when(RemoteHostEndPoint.getHypervisorHostEndPoint(host)).thenReturn(ep);
DataTO dataTO = Mockito.mock(DataTO.class);
CreateObjectAnswer createAnswer = new CreateObjectAnswer(dataTO);
doReturn(createAnswer).when(scaleIOPrimaryDataStoreDriver).createVolume(destData, 2L, true);
when(dataTO.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
doReturn(true).when(scaleIOPrimaryDataStoreDriver)
.grantAccess(any(), any(), any());
when(configDao.getValue(Config.MigrateWait.key())).thenReturn("3600");
MigrateVolumeAnswer migrateVolumeAnswer = Mockito.mock(MigrateVolumeAnswer.class);
when(ep.sendMessage(any())).thenReturn(migrateVolumeAnswer);
when(migrateVolumeAnswer.getResult()).thenReturn(false);
Mockito.doNothing().when(scaleIOPrimaryDataStoreDriver)
.revertBlockCopyVolumeOperations(any(), any(), any(), any());
Answer answer = scaleIOPrimaryDataStoreDriver.liveMigrateVolume(srcData, destData);
Assert.assertFalse(answer.getResult());
}
private void fillSrcVolumeDetails(VolumeInfo srcData, DataStore srcStore) {
when(srcStore.getId()).thenReturn(1L);
when(srcData.getId()).thenReturn(1L);
StoragePoolVO storagePoolVO = Mockito.mock(StoragePoolVO.class);
when(storagePoolDao.findById(1L)).thenReturn(storagePoolVO);
when(storagePoolVO.isManaged()).thenReturn(true);
VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
when(volumeDao.findById(1L)).thenReturn(volumeVO);
when(volumeDetailsDao.findDetail(1L, DiskTO.SCSI_NAA_DEVICE_ID)).thenReturn(null);
when(volumeService.getChapInfo(srcData, srcStore)).thenReturn(null);
StoragePoolDetailVO srcStoragePoolDetail = Mockito.mock(StoragePoolDetailVO.class);
when(srcStoragePoolDetail.getValue()).thenReturn("610204d03e3ad60f");
when(storagePoolDetailsDao.findDetail(1L, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcStoragePoolDetail);
}
private void fillDestVolumeDetails(VolumeInfo srcData, DataStore srcStore) {
when(srcStore.getId()).thenReturn(2L);
when(srcData.getId()).thenReturn(2L);
StoragePoolVO storagePoolVO = Mockito.mock(StoragePoolVO.class);
when(storagePoolDao.findById(2L)).thenReturn(storagePoolVO);
when(storagePoolVO.isManaged()).thenReturn(true);
VolumeVO volumeVO = Mockito.mock(VolumeVO.class);
when(volumeDao.findById(2L)).thenReturn(volumeVO);
when(volumeDetailsDao.findDetail(2L, DiskTO.SCSI_NAA_DEVICE_ID)).thenReturn(null);
when(volumeService.getChapInfo(srcData, srcStore)).thenReturn(null);
StoragePoolDetailVO srcStoragePoolDetail = Mockito.mock(StoragePoolDetailVO.class);
when(srcStoragePoolDetail.getValue()).thenReturn("7332760565f6340f");
when(storagePoolDetailsDao.findDetail(2L, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID)).thenReturn(srcStoragePoolDetail);
}
private Host prepareEndpointForVolumeOperation(VolumeInfo srcData) {
VMInstanceVO instance = Mockito.mock(VMInstanceVO.class);
when(srcData.getAttachedVmName()).thenReturn("i-2-VM");
when(vmInstanceDao.findVMByInstanceName("i-2-VM")).thenReturn(instance);
when(instance.getHostId()).thenReturn(4L);
when(instance.getState()).thenReturn(VirtualMachine.State.Running);
HostVO host = Mockito.mock(HostVO.class);
when(hostDao.findById(4L)).thenReturn(host);
return host;
}
@Test
public void updateVolumeAfterCopyVolumeLiveMigrate() {
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
VolumeInfo destData = Mockito.mock(VolumeInfo.class);
when(srcData.getId()).thenReturn(1L);
when(destData.getId()).thenReturn(1L);
VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", "root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
volume.setPoolId(2L);
when(volumeDao.findById(1L)).thenReturn(volume);
when(volumeDao.update(1L, volume)).thenReturn(true);
scaleIOPrimaryDataStoreDriver.updateVolumeAfterCopyVolume(srcData, destData);
Assert.assertEquals(Optional.of(2L), Optional.of(volume.getLastPoolId()));
}
@Test
public void updateVolumeAfterCopyVolumeOffline() {
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
VolumeInfo destData = Mockito.mock(VolumeInfo.class);
when(srcData.getId()).thenReturn(1L);
when(destData.getId()).thenReturn(2L);
VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", "root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
when(volumeDao.findById(1L)).thenReturn(volume);
when(volumeDao.update(1L, volume)).thenReturn(true);
scaleIOPrimaryDataStoreDriver.updateVolumeAfterCopyVolume(srcData, destData);
Assert.assertNull(volume.get_iScsiName());
Assert.assertNull(volume.getPath());
Assert.assertNull(volume.getFolder());
}
@Test
public void revertBlockCopyVolumeOperationsOnDeleteSuccess() throws Exception{
//Either destination volume delete success or failure, DB operations should get revert
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
VolumeInfo destData = Mockito.mock(VolumeInfo.class);
Host host = Mockito.mock(Host.class);
String destVolumePath = "01b332b300000007:vol-11-b9e2-10ee";
when(srcData.getId()).thenReturn(1L);
when(srcData.getPoolId()).thenReturn(1L);
when(destData.getId()).thenReturn(1L);
when(srcData.getPath()).thenReturn("bec0ba7700000007:vol-11-6aef-10ee");
when(srcData.getFolder()).thenReturn("921c364500000007");
DataStore destStore = Mockito.mock(DataStore.class);
when(destStore.getId()).thenReturn(2L);
when(destData.getDataStore()).thenReturn(destStore);
doNothing().when(scaleIOPrimaryDataStoreDriver)
.revokeAccess(any(), any(), any());
ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
doReturn(client).when(scaleIOPrimaryDataStoreDriver)
.getScaleIOClient(any());
when(client.deleteVolume(any())).thenReturn(true);
VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", "root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
when(volumeDao.findById(1L)).thenReturn(volume);
when(volumeDao.update(1L, volume)).thenReturn(true);
scaleIOPrimaryDataStoreDriver.revertBlockCopyVolumeOperations(srcData, destData, host, destVolumePath);
Assert.assertEquals("bec0ba7700000007:vol-11-6aef-10ee", volume.get_iScsiName());
Assert.assertEquals("bec0ba7700000007:vol-11-6aef-10ee", volume.getPath());
Assert.assertEquals("921c364500000007", volume.getFolder());
}
@Test
public void revertBlockCopyVolumeOperationsOnDeleteFailure() throws Exception{
//Either destination volume delete success or failure, DB operations should get revert
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
VolumeInfo destData = Mockito.mock(VolumeInfo.class);
Host host = Mockito.mock(Host.class);
String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
String destVolumePath = "01b332b300000007:vol-11-b9e2-10ee";
when(srcData.getId()).thenReturn(1L);
when(srcData.getPoolId()).thenReturn(1L);
when(destData.getId()).thenReturn(1L);
when(srcData.getPath()).thenReturn(srcVolumePath);
when(srcData.getFolder()).thenReturn("921c364500000007");
DataStore destStore = Mockito.mock(DataStore.class);
when(destStore.getId()).thenReturn(2L);
when(destData.getDataStore()).thenReturn(destStore);
doNothing().when(scaleIOPrimaryDataStoreDriver).revokeAccess(any(), any(), any());
ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
doReturn(client).when(scaleIOPrimaryDataStoreDriver)
.getScaleIOClient(any());
when(client.deleteVolume(any())).thenReturn(false);
VolumeVO volume = new VolumeVO("root", 1L, 1L, 1L, 1L, 1L, "root", "root", Storage.ProvisioningType.THIN, 1, null, null, "root", Volume.Type.ROOT);
when(volumeDao.findById(1L)).thenReturn(volume);
when(volumeDao.update(1L, volume)).thenReturn(true);
scaleIOPrimaryDataStoreDriver.revertBlockCopyVolumeOperations(srcData, destData, host, destVolumePath);
Assert.assertEquals(srcVolumePath, volume.get_iScsiName());
Assert.assertEquals(srcVolumePath, volume.getPath());
Assert.assertEquals("921c364500000007", volume.getFolder());
}
@Test
public void deleteSourceVolumeSuccessScenarioAfterSuccessfulBlockCopy() throws Exception {
// Either Volume deletion success or failure method should complete
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
Host host = Mockito.mock(Host.class);
String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
DataStore srcStore = Mockito.mock(DataStore.class);
DataTO volumeTO = Mockito.mock(DataTO.class);
when(srcData.getDataStore()).thenReturn(srcStore);
when(srcData.getTO()).thenReturn(volumeTO);
when(volumeTO.getPath()).thenReturn(srcVolumePath);
doNothing().when(scaleIOPrimaryDataStoreDriver).revokeVolumeAccess(any(), any(), any());
ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
doReturn(client).when(scaleIOPrimaryDataStoreDriver)
.getScaleIOClient(any());
when(client.deleteVolume(any())).thenReturn(true);
scaleIOPrimaryDataStoreDriver.deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
}
@Test
public void deleteSourceVolumeFailureScenarioAfterSuccessfulBlockCopy() throws Exception {
// Either Volume deletion success or failure method should complete
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
Host host = Mockito.mock(Host.class);
when(host.getId()).thenReturn(1L);
String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
DataStore srcStore = Mockito.mock(DataStore.class);
when(srcStore.getId()).thenReturn(1L);
DataTO volumeTO = Mockito.mock(DataTO.class);
when(srcData.getDataStore()).thenReturn(srcStore);
when(srcData.getTO()).thenReturn(volumeTO);
when(volumeTO.getPath()).thenReturn(srcVolumePath);
String sdcId = "7332760565f6340f";
doReturn(sdcId).when(scaleIOPrimaryDataStoreDriver).getConnectedSdc(1L, 1L);
ScaleIOGatewayClient client = Mockito.mock(ScaleIOGatewayClient.class);
doReturn(client).when(scaleIOPrimaryDataStoreDriver)
.getScaleIOClient(any());
doReturn(true).when(client).unmapVolumeFromSdc(any(), any());
when(client.deleteVolume(any())).thenReturn(false);
scaleIOPrimaryDataStoreDriver.deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
}
@Test
public void deleteSourceVolumeFailureScenarioWhenNoSDCisFound() {
// Either Volume deletion success or failure method should complete
VolumeInfo srcData = Mockito.mock(VolumeInfo.class);
Host host = Mockito.mock(Host.class);
when(host.getId()).thenReturn(1L);
String srcVolumePath = "bec0ba7700000007:vol-11-6aef-10ee";
DataStore srcStore = Mockito.mock(DataStore.class);
when(srcStore.getId()).thenReturn(1L);
DataTO volumeTO = Mockito.mock(DataTO.class);
when(srcData.getDataStore()).thenReturn(srcStore);
when(srcData.getTO()).thenReturn(volumeTO);
when(volumeTO.getPath()).thenReturn(srcVolumePath);
String sdcId = "7332760565f6340f";
doReturn(null).when(scaleIOPrimaryDataStoreDriver).getConnectedSdc(1L, 1L);
scaleIOPrimaryDataStoreDriver.deleteSourceVolumeAfterSuccessfulBlockCopy(srcData, host);
}
@Test
public void testCopyOfflineVolume() {
when(configDao.getValue(Config.CopyVolumeWait.key())).thenReturn("3600");
DataObject srcData = Mockito.mock(DataObject.class);
DataTO srcDataTO = Mockito.mock(DataTO.class);
when(srcData.getTO()).thenReturn(srcDataTO);
DataObject destData = Mockito.mock(DataObject.class);
DataTO destDataTO = Mockito.mock(DataTO.class);
when(destData.getTO()).thenReturn(destDataTO);
Host destHost = Mockito.mock(Host.class);
doReturn(false).when(scaleIOPrimaryDataStoreDriver).anyVolumeRequiresEncryption(srcData, destData);
PowerMockito.mockStatic(RemoteHostEndPoint.class);
RemoteHostEndPoint ep = Mockito.mock(RemoteHostEndPoint.class);
when(RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(ep);
Answer answer = Mockito.mock(Answer.class);
when(ep.sendMessage(any())).thenReturn(answer);
Answer expectedAnswer = scaleIOPrimaryDataStoreDriver.copyOfflineVolume(srcData, destData, destHost);
Assert.assertEquals(expectedAnswer, answer);
}
@Test
public void testCopyOfflineVolumeFailureWhenNoEndpointFound() {
when(configDao.getValue(Config.CopyVolumeWait.key())).thenReturn("3600");
DataObject srcData = Mockito.mock(DataObject.class);
DataTO srcDataTO = Mockito.mock(DataTO.class);
when(srcData.getTO()).thenReturn(srcDataTO);
DataObject destData = Mockito.mock(DataObject.class);
DataTO destDataTO = Mockito.mock(DataTO.class);
when(destData.getTO()).thenReturn(destDataTO);
Host destHost = Mockito.mock(Host.class);
doReturn(false).when(scaleIOPrimaryDataStoreDriver).anyVolumeRequiresEncryption(srcData, destData);
PowerMockito.mockStatic(RemoteHostEndPoint.class);
when(RemoteHostEndPoint.getHypervisorHostEndPoint(destHost)).thenReturn(null);
Answer answer = scaleIOPrimaryDataStoreDriver.copyOfflineVolume(srcData, destData, destHost);
Assert.assertEquals(false, answer.getResult());
}
}

View File

@ -92,6 +92,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
@ -326,6 +327,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Inject @Inject
protected ProjectManager projectManager; protected ProjectManager projectManager;
@Inject
protected StoragePoolDetailsDao storagePoolDetailsDao;
protected Gson _gson; protected Gson _gson;
@ -1098,8 +1101,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
if (isNotPossibleToResize(volume, diskOffering)) { if (isNotPossibleToResize(volume, diskOffering)) {
throw new InvalidParameterValueException( throw new InvalidParameterValueException(
"Failed to resize Root volume. The service offering of this Volume has been configured with a root disk size; " "Failed to resize Root volume. The service offering of this Volume has been configured with a root disk size; "
+ "on such case a Root Volume can only be resized when changing to another Service Offering with a Root disk size. " + "on such case a Root Volume can only be resized when changing to another Service Offering with a Root disk size. "
+ "For more details please check out the Official Resizing Volumes documentation."); + "For more details please check out the Official Resizing Volumes documentation.");
} }
// convert from bytes to GiB // convert from bytes to GiB
@ -1246,7 +1249,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
*/ */
if (currentSize > newSize && !shrinkOk) { if (currentSize > newSize && !shrinkOk) {
throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume."
+ "Need to sign off by supplying the shrinkok parameter with value of true."); + "Need to sign off by supplying the shrinkok parameter with value of true.");
} }
if (newSize > currentSize) { if (newSize > currentSize) {
@ -2966,10 +2969,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
vm = _vmInstanceDao.findById(instanceId); vm = _vmInstanceDao.findById(instanceId);
} }
if (vol.getPassphraseId() != null) {
throw new InvalidParameterValueException("Migration of encrypted volumes is unsupported");
}
// Check that Vm to which this volume is attached does not have VM Snapshots // Check that Vm to which this volume is attached does not have VM Snapshots
// OfflineVmwareMigration: consider if this is needed and desirable // OfflineVmwareMigration: consider if this is needed and desirable
if (vm != null && _vmSnapshotDao.findByVm(vm.getId()).size() > 0) { if (vm != null && _vmSnapshotDao.findByVm(vm.getId()).size() > 0) {
@ -2983,11 +2982,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported");
} }
StoragePoolVO storagePoolVO = _storagePoolDao.findById(vol.getPoolId());
if (storagePoolVO.getPoolType() == Storage.StoragePoolType.PowerFlex) {
throw new InvalidParameterValueException("Migrate volume of a running VM is unsupported on storage pool type " + storagePoolVO.getPoolType());
}
// Check if the underlying hypervisor supports storage motion. // Check if the underlying hypervisor supports storage motion.
Long hostId = vm.getHostId(); Long hostId = vm.getHostId();
if (hostId != null) { if (hostId != null) {
@ -3002,7 +2996,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
liveMigrateVolume = capabilities.isStorageMotionSupported(); liveMigrateVolume = capabilities.isStorageMotionSupported();
} }
if (liveMigrateVolume && HypervisorType.KVM.equals(host.getHypervisorType())) { StoragePoolVO storagePoolVO = _storagePoolDao.findById(vol.getPoolId());
if (liveMigrateVolume && HypervisorType.KVM.equals(host.getHypervisorType()) && !storagePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
StoragePoolVO destinationStoragePoolVo = _storagePoolDao.findById(storagePoolId); StoragePoolVO destinationStoragePoolVo = _storagePoolDao.findById(storagePoolId);
if (isSourceOrDestNotOnStorPool(storagePoolVO, destinationStoragePoolVo)) { if (isSourceOrDestNotOnStorPool(storagePoolVO, destinationStoragePoolVo)) {

View File

@ -6270,16 +6270,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
+ " hypervisors: [%s].", hypervisorType, HYPERVISORS_THAT_CAN_DO_STORAGE_MIGRATION_ON_NON_USER_VMS)); + " hypervisors: [%s].", hypervisorType, HYPERVISORS_THAT_CAN_DO_STORAGE_MIGRATION_ON_NON_USER_VMS));
} }
List<VolumeVO> vols = _volsDao.findByInstance(vm.getId());
if (vols.size() > 1) {
// OffLineVmwareMigration: data disks are not permitted, here!
if (vols.size() > 1 &&
// OffLineVmwareMigration: allow multiple disks for vmware
!HypervisorType.VMware.equals(hypervisorType)) {
throw new InvalidParameterValueException("Data disks attached to the vm, can not migrate. Need to detach data disks first");
}
}
// Check that Vm does not have VM Snapshots // Check that Vm does not have VM Snapshots
if (_vmSnapshotDao.findByVm(vmId).size() > 0) { if (_vmSnapshotDao.findByVm(vmId).size() > 0) {
throw new InvalidParameterValueException("VM's disk cannot be migrated, please remove all the VM Snapshots for this VM"); throw new InvalidParameterValueException("VM's disk cannot be migrated, please remove all the VM Snapshots for this VM");

View File

@ -1144,6 +1144,249 @@ class TestScaleIOVolumes(cloudstackTestCase):
test_virtual_machine.delete(self.apiClient, True) test_virtual_machine.delete(self.apiClient, True)
@attr(tags=['advanced', 'migration'], required_hardware=False)
def test_11_live_migrate_volume_to_same_instance_pool(self):
'''Migrate volume to the same instance pool'''
if not TestData.migrationTests:
self.skipTest("Volume migration tests not enabled, skipping test")
#######################################
# STEP 1: Create VM and Start VM #
#######################################
test_virtual_machine = VirtualMachine.create(
self.apiClient,
self.testdata[TestData.virtualMachine3],
accountid=self.account.name,
zoneid=self.zone.id,
serviceofferingid=self.compute_offering.id,
templateid=self.template.id,
domainid=self.domain.id,
startvm=False
)
TestScaleIOVolumes._start_vm(test_virtual_machine)
#######################################
# STEP 2: Create vol and attach to VM #
#######################################
new_volume = Volume.create(
self.apiClient,
self.testdata[TestData.volume_3],
account=self.account.name,
domainid=self.domain.id,
zoneid=self.zone.id,
diskofferingid=self.disk_offering_same_inst.id
)
volume_to_delete_later = new_volume
new_volume = test_virtual_machine.attach_volume(
self.apiClient,
new_volume
)
vm = self._get_vm(test_virtual_machine.id)
self.assertEqual(
new_volume.virtualmachineid,
vm.id,
"Check if attached to virtual machine"
)
self.assertEqual(
vm.state.lower(),
'running',
str(vm.state)
)
#######################################
# STEP 3: Migrate volume #
#######################################
pools = StoragePool.listForMigration(
self.apiClient,
id=new_volume.id
)
if not pools:
self.skipTest("No suitable storage pools found for volume migration, skipping test")
self.assertEqual(
validateList(pools)[0],
PASS,
"Invalid pool response from findStoragePoolsForMigration API"
)
pool = pools[0]
self.debug("Migrating Volume-ID: %s to Same Instance Pool: %s" % (new_volume.id, pool.id))
try:
Volume.migrate(
self.apiClient,
volumeid=new_volume.id,
storageid=pool.id
)
except Exception as e:
self.fail("Volume migration failed with error %s" % e)
#######################################
# STEP 4: Detach and delete volume #
#######################################
new_volume = test_virtual_machine.detach_volume(
self.apiClient,
new_volume
)
self.assertEqual(
new_volume.virtualmachineid,
None,
"Check if attached to virtual machine"
)
volume_to_delete_later.delete(self.apiClient)
list_volumes_response = list_volumes(
self.apiClient,
id=new_volume.id
)
self.assertEqual(
list_volumes_response,
None,
"Check volume was deleted"
)
#######################################
# STEP 4: Delete VM #
#######################################
test_virtual_machine.delete(self.apiClient, True)
@attr(tags=['advanced', 'migration'], required_hardware=False)
def test_12_migrate_volume_to_distinct_instance_pool(self):
'''Migrate volume to distinct instance pool'''
if not TestData.migrationTests:
self.skipTest("Volume migration tests not enabled, skipping test")
#######################################
# STEP 1: Create VM and Start VM #
#######################################
test_virtual_machine = VirtualMachine.create(
self.apiClient,
self.testdata[TestData.virtualMachine4],
accountid=self.account.name,
zoneid=self.zone.id,
serviceofferingid=self.compute_offering.id,
templateid=self.template.id,
domainid=self.domain.id,
startvm=False
)
TestScaleIOVolumes._start_vm(test_virtual_machine)
#######################################
# STEP 2: Create vol and attach to VM #
#######################################
new_volume = Volume.create(
self.apiClient,
self.testdata[TestData.volume_4],
account=self.account.name,
domainid=self.domain.id,
zoneid=self.zone.id,
diskofferingid=self.disk_offering_distinct_inst.id
)
volume_to_delete_later = new_volume
new_volume = test_virtual_machine.attach_volume(
self.apiClient,
new_volume
)
vm = self._get_vm(test_virtual_machine.id)
self.assertEqual(
new_volume.virtualmachineid,
vm.id,
"Check if attached to virtual machine"
)
self.assertEqual(
vm.state.lower(),
'running',
str(vm.state)
)
#######################################
# STEP 3: Migrate volume #
#######################################
pools = StoragePool.listForMigration(
self.apiClient,
id=new_volume.id
)
if not pools:
self.skipTest("No suitable storage pools found for volume migration, skipping test")
self.assertEqual(
validateList(pools)[0],
PASS,
"Invalid pool response from findStoragePoolsForMigration API"
)
pool = pools[0]
self.debug("Migrating Volume-ID: %s to Distinct Instance Pool: %s" % (new_volume.id, pool.id))
try:
Volume.migrate(
self.apiClient,
volumeid=new_volume.id,
storageid=pool.id
)
except Exception as e:
self.fail("Volume migration failed with error %s" % e)
#######################################
# STEP 4: Detach and delete volume #
#######################################
new_volume = test_virtual_machine.detach_volume(
self.apiClient,
new_volume
)
self.assertEqual(
new_volume.virtualmachineid,
None,
"Check if attached to virtual machine"
)
volume_to_delete_later.delete(self.apiClient)
list_volumes_response = list_volumes(
self.apiClient,
id=new_volume.id
)
self.assertEqual(
list_volumes_response,
None,
"Check volume was deleted"
)
#######################################
# STEP 4: Delete VM #
#######################################
test_virtual_machine.delete(self.apiClient, True)
def _create_vm_using_template_and_destroy_vm(self, template): def _create_vm_using_template_and_destroy_vm(self, template):
vm_name = "VM-%d" % random.randint(0, 100) vm_name = "VM-%d" % random.randint(0, 100)