Allow KVM VM live migration with ROOT volume on file storage type (#2997)

* Allow KVM VM live migration with ROOT volume on file

* Allow KVM VM live migration with ROOT volume on file
- Add JUnit tests

* Address reviewers and change some variable names to ease future
implementation (developers can easily guess the name and use
autocomplete)
This commit is contained in:
Gabriel Beims Bräscher 2018-12-14 09:01:28 -02:00 committed by Rafael Weingärtner
parent ecd2b95d49
commit bf209405e7
17 changed files with 917 additions and 122 deletions

View File

@ -28,6 +28,7 @@ public class VirtualMachineTO {
private long id;
private String name;
private BootloaderType bootloader;
private VirtualMachine.State state;
Type type;
int cpus;
@ -147,6 +148,14 @@ public class VirtualMachineTO {
this.bootloader = bootloader;
}
public VirtualMachine.State getState() {
return state;
}
public void setState(VirtualMachine.State state) {
this.state = state;
}
public int getCpus() {
return cpus;
}

View File

@ -72,6 +72,7 @@ public class DiskProfile {
offering.isCustomized(),
null);
this.hyperType = hyperType;
this.provisioningType = offering.getProvisioningType();
}
public DiskProfile(DiskProfile dp) {

View File

@ -19,7 +19,9 @@
package com.cloud.agent.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.cloud.agent.api.to.VirtualMachineTO;
@ -33,6 +35,7 @@ public class MigrateCommand extends Command {
private boolean isWindows;
private VirtualMachineTO vmTO;
private boolean executeInSequence = false;
private List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
protected MigrateCommand() {
}
@ -90,6 +93,14 @@ public class MigrateCommand extends Command {
return executeInSequence;
}
public List<MigrateDiskInfo> getMigrateDiskInfoList() {
return migrateDiskInfoList;
}
public void setMigrateDiskInfoList(List<MigrateDiskInfo> migrateDiskInfoList) {
this.migrateDiskInfoList = migrateDiskInfoList;
}
public static class MigrateDiskInfo {
public enum DiskType {
FILE, BLOCK;
@ -123,6 +134,7 @@ public class MigrateCommand extends Command {
private final DriverType driverType;
private final Source source;
private final String sourceText;
private boolean isSourceDiskOnStorageFileSystem;
public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText) {
this.serialNumber = serialNumber;
@ -151,5 +163,13 @@ public class MigrateCommand extends Command {
public String getSourceText() {
return sourceText;
}
public boolean isSourceDiskOnStorageFileSystem() {
return isSourceDiskOnStorageFileSystem;
}
public void setSourceDiskOnStorageFileSystem(boolean isDiskOnFileSystemStorage) {
this.isSourceDiskOnStorageFileSystem = isDiskOnFileSystemStorage;
}
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.motion;
import java.io.File;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
import com.cloud.agent.api.storage.CreateAnswer;
import com.cloud.agent.api.storage.CreateCommand;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.host.Host;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.VolumeVO;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.DiskProfile;
/**
* Extends {@link StorageSystemDataMotionStrategy}, allowing KVM hosts to migrate VMs with the ROOT volume on a non managed local storage pool.
* As {@link StorageSystemDataMotionStrategy} is considering KVM, this implementation also migrates only from/to KVM hosts.
*/
public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMotionStrategy {
@Inject
private TemplateDataFactory templateDataFactory;
/**
* Uses the canHandle from the Super class {@link StorageSystemDataMotionStrategy}. If the storage pool is of file and the internalCanHandle from {@link StorageSystemDataMotionStrategy} CANT_HANDLE, returns the StrategyPriority.HYPERVISOR strategy priority. otherwise returns CANT_HANDLE.
* Note that the super implementation (override) is called by {@link #canHandle(Map, Host, Host)} which ensures that {@link #internalCanHandle(Map)} will be executed only if the source host is KVM.
*/
@Override
protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap) {
if (super.internalCanHandle(volumeMap) == StrategyPriority.CANT_HANDLE) {
Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
for (VolumeInfo volumeInfo : volumeInfoSet) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
if (storagePoolVO.getPoolType() != StoragePoolType.Filesystem && storagePoolVO.getPoolType() != StoragePoolType.NetworkFilesystem) {
return StrategyPriority.CANT_HANDLE;
}
}
return StrategyPriority.HYPERVISOR;
}
return StrategyPriority.CANT_HANDLE;
}
/**
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume and calls rootImageProvisioning.
*/
@Override
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath) {
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
MigrateCommand.MigrateDiskInfo.Source.FILE, destPath);
}
/**
* Generates the volume path by appending the Volume UUID to the Libvirt destiny images path.</br>
* Example: /var/lib/libvirt/images/f3d49ecc-870c-475a-89fa-fd0124420a9b
*/
@Override
protected String generateDestPath(VirtualMachineTO vmTO, VolumeVO srcVolume, Host destHost, StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
DiskOfferingVO diskOffering = _diskOfferingDao.findById(srcVolume.getDiskOfferingId());
DiskProfile diskProfile = new DiskProfile(destVolumeInfo, diskOffering, HypervisorType.KVM);
String templateUuid = getTemplateUuid(destVolumeInfo.getTemplateId());
CreateCommand rootImageProvisioningCommand = new CreateCommand(diskProfile, templateUuid, destStoragePool, true);
Answer rootImageProvisioningAnswer = _agentMgr.easySend(destHost.getId(), rootImageProvisioningCommand);
if (rootImageProvisioningAnswer == null) {
throw new CloudRuntimeException(String.format("Migration with storage of vm [%s] failed while provisioning root image", vmTO.getName()));
}
if (!rootImageProvisioningAnswer.getResult()) {
throw new CloudRuntimeException(String.format("Unable to modify target volume on the host [host id:%s, name:%s]", destHost.getId(), destHost.getName()));
}
String libvirtDestImgsPath = null;
if (rootImageProvisioningAnswer instanceof CreateAnswer) {
libvirtDestImgsPath = ((CreateAnswer)rootImageProvisioningAnswer).getVolume().getName();
}
// File.getAbsolutePath is used to keep the file separator as it should be and eliminate a verification to check if exists a file separator in the last character of libvirtDestImgsPath.
return new File(libvirtDestImgsPath, destVolumeInfo.getUuid()).getAbsolutePath();
}
/**
* Returns the template UUID with the given id. If the template ID is null, it returns null.
*/
protected String getTemplateUuid(Long templateId) {
if (templateId == null) {
return null;
}
TemplateInfo templateImage = templateDataFactory.getTemplate(templateId, DataStoreRole.Image);
return templateImage.getUuid();
}
/**
* Sets the volume path as the volume UUID.
*/
@Override
protected void setVolumePath(VolumeVO volume) {
volume.setPath(volume.getUuid());
}
/**
* Return true if the volume should be migrated. Currently only supports migrating volumes on storage pool of the type StoragePoolType.Filesystem.
* This ensures that volumes on shared storage are not migrated and those on local storage pools are migrated.
*/
@Override
protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, Host destHost, StoragePoolVO destStoragePool) {
return sourceStoragePool.getPoolType() == StoragePoolType.Filesystem;
}
}

View File

@ -24,6 +24,7 @@ import com.cloud.agent.api.storage.CopyVolumeAnswer;
import com.cloud.agent.api.storage.CopyVolumeCommand;
import com.cloud.agent.api.MigrateAnswer;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
import com.cloud.agent.api.ModifyTargetsAnswer;
import com.cloud.agent.api.ModifyTargetsCommand;
import com.cloud.agent.api.PrepareForMigrationCommand;
@ -41,7 +42,6 @@ import com.cloud.exception.OperationTimedoutException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostDetailsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.resource.ResourceState;
import com.cloud.storage.DataStoreRole;
@ -49,6 +49,7 @@ import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Snapshot;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateVO;
@ -136,16 +137,18 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
private static final int LOCK_TIME_IN_SECONDS = 300;
private static final String OPERATION_NOT_SUPPORTED = "This operation is not supported.";
@Inject private AgentManager _agentMgr;
@Inject
protected AgentManager _agentMgr;
@Inject private ConfigurationDao _configDao;
@Inject private DataStoreManager dataStoreMgr;
@Inject private DiskOfferingDao _diskOfferingDao;
@Inject
protected DiskOfferingDao _diskOfferingDao;
@Inject private GuestOSCategoryDao _guestOsCategoryDao;
@Inject private GuestOSDao _guestOsDao;
@Inject private ClusterDao clusterDao;
@Inject private HostDao _hostDao;
@Inject private HostDetailsDao hostDetailsDao;
@Inject private PrimaryDataStoreDao _storagePoolDao;
@Inject
protected PrimaryDataStoreDao _storagePoolDao;
@Inject private SnapshotDao _snapshotDao;
@Inject private SnapshotDataStoreDao _snapshotDataStoreDao;
@Inject private SnapshotDetailsDao _snapshotDetailsDao;
@ -251,29 +254,36 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
}
@Override
public StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
public final StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) {
if (HypervisorType.KVM.equals(srcHost.getHypervisorType())) {
Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
return internalCanHandle(volumeMap);
}
return StrategyPriority.CANT_HANDLE;
}
for (VolumeInfo volumeInfo : volumeInfoSet) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
/**
* Handles migrating volumes on managed Storage.
*/
protected StrategyPriority internalCanHandle(Map<VolumeInfo, DataStore> volumeMap) {
Set<VolumeInfo> volumeInfoSet = volumeMap.keySet();
if (storagePoolVO.isManaged()) {
return StrategyPriority.HIGHEST;
}
}
for (VolumeInfo volumeInfo : volumeInfoSet) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(volumeInfo.getPoolId());
Collection<DataStore> dataStores = volumeMap.values();
for (DataStore dataStore : dataStores) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
if (storagePoolVO.isManaged()) {
return StrategyPriority.HIGHEST;
}
if (storagePoolVO.isManaged()) {
return StrategyPriority.HIGHEST;
}
}
Collection<DataStore> dataStores = volumeMap.values();
for (DataStore dataStore : dataStores) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStore.getId());
if (storagePoolVO.isManaged()) {
return StrategyPriority.HIGHEST;
}
}
return StrategyPriority.CANT_HANDLE;
}
@ -1677,10 +1687,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
/**
* For each disk to migrate:
* Create a volume on the target storage system.
* Make the newly created volume accessible to the target KVM host.
* Send a command to the target KVM host to connect to the newly created volume.
* Send a command to the source KVM host to migrate the VM and its storage.
* <ul>
* <li>Create a volume on the target storage system.</li>
* <li>Make the newly created volume accessible to the target KVM host.</li>
* <li>Send a command to the target KVM host to connect to the newly created volume.</li>
* <li>Send a command to the source KVM host to migrate the VM and its storage.</li>
* </ul>
*/
@Override
public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
@ -1693,6 +1705,10 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
verifyLiveMigrationMapForKVM(volumeDataStoreMap);
VMInstanceVO vmInstance = _vmDao.findById(vmTO.getId());
vmTO.setState(vmInstance.getState());
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<MigrateDiskInfo>();
Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
@ -1702,6 +1718,11 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
VolumeVO srcVolume = _volumeDao.findById(srcVolumeInfo.getId());
StoragePoolVO destStoragePool = _storagePoolDao.findById(destDataStore.getId());
StoragePoolVO sourceStoragePool = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
if (!shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool)) {
continue;
}
VolumeVO destVolume = duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
VolumeInfo destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
@ -1718,7 +1739,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
destVolume = _volumeDao.findById(destVolume.getId());
destVolume.setPath(destVolume.get_iScsiName());
setVolumePath(destVolume);
_volumeDao.update(destVolume.getId(), destVolume);
@ -1728,13 +1749,11 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
_volumeService.grantAccess(destVolumeInfo, destHost, destDataStore);
String connectedPath = connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
String destPath = generateDestPath(vmTO, srcVolume, destHost, destStoragePool, destVolumeInfo);
MigrateCommand.MigrateDiskInfo migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
MigrateCommand.MigrateDiskInfo.DriverType.RAW,
MigrateCommand.MigrateDiskInfo.Source.DEV,
connectedPath);
MigrateCommand.MigrateDiskInfo migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath);
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
migrateDiskInfoList.add(migrateDiskInfo);
migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
@ -1765,6 +1784,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
migrateCommand.setWait(StorageManager.KvmStorageOnlineMigrationWait.value());
migrateCommand.setMigrateStorage(migrateStorage);
migrateCommand.setMigrateDiskInfoList(migrateDiskInfoList);
String autoConvergence = _configDao.getValue(Config.KvmAutoConvergence.toString());
boolean kvmAutoConvergence = Boolean.parseBoolean(autoConvergence);
@ -1803,6 +1823,42 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
}
}
/**
* Returns true. This method was implemented considering the classes that extend this {@link StorageSystemDataMotionStrategy} and cannot migrate volumes from certain types of source storage pools and/or to a different kind of destiny storage pool.
*/
protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, Host destHost, StoragePoolVO destStoragePool) {
return true;
}
/**
* Returns true if the storage pool type is {@link StoragePoolType.Filesystem}.
*/
protected boolean isStoragePoolTypeOfFile(StoragePoolVO sourceStoragePool) {
return sourceStoragePool.getPoolType() == StoragePoolType.Filesystem;
}
/**
* Returns the iScsi connection path.
*/
protected String generateDestPath(VirtualMachineTO vmTO, VolumeVO srcVolume, Host destHost, StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
return connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
}
/**
* Configures a {@link MigrateDiskInfo} object with disk type of BLOCK, Driver type RAW and Source DEV
*/
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath) {
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, MigrateCommand.MigrateDiskInfo.DriverType.RAW,
MigrateCommand.MigrateDiskInfo.Source.DEV, destPath);
}
/**
* Sets the volume path as the iScsi name in case of a configured iScsi.
*/
protected void setVolumePath(VolumeVO volume) {
volume.setPath(volume.get_iScsiName());
}
private void handlePostMigration(boolean success, Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host destHost) {
if (!success) {
try {
@ -1913,7 +1969,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
return _volumeDao.persist(newVol);
}
private String connectHostToVolume(Host host, long storagePoolId, String iqn) {
protected String connectHostToVolume(Host host, long storagePoolId, String iqn) {
ModifyTargetsCommand modifyTargetsCommand = getModifyTargetsCommand(storagePoolId, iqn, true);
return sendModifyTargetsCommand(modifyTargetsCommand, host.getId()).get(0);
@ -1990,10 +2046,6 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
if (destStoragePoolVO == null) {
throw new CloudRuntimeException("Destination storage pool with ID " + dataStore.getId() + " was not located.");
}
if (!destStoragePoolVO.isManaged()) {
throw new CloudRuntimeException("Migrating a volume online with KVM can currently only be done when moving to managed storage.");
}
}
}

View File

@ -34,4 +34,6 @@
class="org.apache.cloudstack.storage.motion.HypervStorageMotionStrategy" />
<bean id="storageSystemDataMotionStrategy"
class="org.apache.cloudstack.storage.motion.StorageSystemDataMotionStrategy" />
<bean id="kvmNonManagedStorageSystemDataMotionStrategy"
class="org.apache.cloudstack.storage.motion.KvmNonManagedStorageDataMotionStrategy" />
</beans>

View File

@ -0,0 +1,256 @@
/*
* 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.motion;
import java.util.HashMap;
import java.util.Map;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.storage.CreateAnswer;
import com.cloud.agent.api.storage.CreateCommand;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.agent.api.to.VolumeTO;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.DiskProfile;
@RunWith(MockitoJUnitRunner.class)
public class KvmNonManagedStorageSystemDataMotionTest {
@Mock
private PrimaryDataStoreDao primaryDataStoreDao;
@Mock
private TemplateDataFactory templateDataFactory;
@Mock
private AgentManager agentManager;
@Mock
private DiskOfferingDao diskOfferingDao;
@Spy
@InjectMocks
private KvmNonManagedStorageDataMotionStrategy kvmNonManagedStorageDataMotionStrategy;
@Test
public void canHandleTestExpectHypervisorStrategyForKvm() {
canHandleExpectCannotHandle(HypervisorType.KVM, 1, StrategyPriority.HYPERVISOR);
}
@Test
public void canHandleTestExpectCannotHandle() {
HypervisorType[] hypervisorTypeArray = HypervisorType.values();
for (int i = 0; i < hypervisorTypeArray.length; i++) {
HypervisorType ht = hypervisorTypeArray[i];
if (ht.equals(HypervisorType.KVM)) {
continue;
}
canHandleExpectCannotHandle(ht, 0, StrategyPriority.CANT_HANDLE);
}
}
private void canHandleExpectCannotHandle(HypervisorType hypervisorType, int times, StrategyPriority expectedStrategyPriority) {
HostVO srcHost = new HostVO("sourceHostUuid");
srcHost.setHypervisorType(hypervisorType);
Mockito.doReturn(StrategyPriority.HYPERVISOR).when(kvmNonManagedStorageDataMotionStrategy).internalCanHandle(new HashMap<>());
StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.canHandle(new HashMap<>(), srcHost, new HostVO("destHostUuid"));
Mockito.verify(kvmNonManagedStorageDataMotionStrategy, Mockito.times(times)).internalCanHandle(new HashMap<>());
Assert.assertEquals(expectedStrategyPriority, strategyPriority);
}
@Test
public void internalCanHandleTestNonManaged() {
StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
for (int i = 0; i < storagePoolTypeArray.length; i++) {
Map<VolumeInfo, DataStore> volumeMap = configureTestInternalCanHandle(false, storagePoolTypeArray[i]);
StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.internalCanHandle(volumeMap);
if (storagePoolTypeArray[i] == StoragePoolType.Filesystem || storagePoolTypeArray[i] == StoragePoolType.NetworkFilesystem) {
Assert.assertEquals(StrategyPriority.HYPERVISOR, strategyPriority);
} else {
Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
}
}
}
@Test
public void internalCanHandleTestIsManaged() {
StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
for (int i = 0; i < storagePoolTypeArray.length; i++) {
Map<VolumeInfo, DataStore> volumeMap = configureTestInternalCanHandle(true, storagePoolTypeArray[i]);
StrategyPriority strategyPriority = kvmNonManagedStorageDataMotionStrategy.internalCanHandle(volumeMap);
Assert.assertEquals(StrategyPriority.CANT_HANDLE, strategyPriority);
}
}
private Map<VolumeInfo, DataStore> configureTestInternalCanHandle(boolean isManagedStorage, StoragePoolType storagePoolType) {
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn(0l).when(volumeInfo).getPoolId();
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.doReturn(0l).when(ds).getId();
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
StoragePoolVO storagePool = Mockito.spy(new StoragePoolVO());
Mockito.doReturn(storagePoolType).when(storagePool).getPoolType();
Mockito.doReturn(storagePool).when(primaryDataStoreDao).findById(0l);
Mockito.doReturn(isManagedStorage).when(storagePool).isManaged();
return volumeMap;
}
@Test
public void getTemplateUuidTestTemplateIdNotNull() {
String expectedTemplateUuid = prepareTestGetTemplateUuid();
String templateUuid = kvmNonManagedStorageDataMotionStrategy.getTemplateUuid(0l);
Assert.assertEquals(expectedTemplateUuid, templateUuid);
}
@Test
public void getTemplateUuidTestTemplateIdNull() {
prepareTestGetTemplateUuid();
String templateUuid = kvmNonManagedStorageDataMotionStrategy.getTemplateUuid(null);
Assert.assertEquals(null, templateUuid);
}
private String prepareTestGetTemplateUuid() {
TemplateInfo templateImage = Mockito.mock(TemplateInfo.class);
String expectedTemplateUuid = "template uuid";
Mockito.when(templateImage.getUuid()).thenReturn(expectedTemplateUuid);
Mockito.doReturn(templateImage).when(templateDataFactory).getTemplate(0l, DataStoreRole.Image);
return expectedTemplateUuid;
}
@Test
public void configureMigrateDiskInfoTest() {
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
MigrateCommand.MigrateDiskInfo migrateDiskInfo = kvmNonManagedStorageDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, migrateDiskInfo.getDiskType());
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, migrateDiskInfo.getDriverType());
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, migrateDiskInfo.getSource());
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
}
@Test
public void generateDestPathTest() {
configureAndVerifygenerateDestPathTest(true, false);
}
@Test(expected = CloudRuntimeException.class)
public void generateDestPathTestExpectCloudRuntimeException() {
configureAndVerifygenerateDestPathTest(false, false);
}
@Test(expected = CloudRuntimeException.class)
public void generateDestPathTestExpectCloudRuntimeException2() {
configureAndVerifygenerateDestPathTest(false, true);
}
private void configureAndVerifygenerateDestPathTest(boolean answerResult, boolean answerIsNull) {
String uuid = "f3d49ecc-870c-475a-89fa-fd0124420a9b";
String destPath = "/var/lib/libvirt/images/";
VirtualMachineTO vmTO = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTO.getName()).thenReturn("vmName");
VolumeVO srcVolume = Mockito.spy(new VolumeVO("name", 0l, 0l, 0l, 0l, 0l, "folder", "path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT));
StoragePoolVO destStoragePool = Mockito.spy(new StoragePoolVO());
VolumeInfo destVolumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn(0l).when(destVolumeInfo).getTemplateId();
Mockito.doReturn(0l).when(destVolumeInfo).getId();
Mockito.doReturn(Volume.Type.ROOT).when(destVolumeInfo).getVolumeType();
Mockito.doReturn("name").when(destVolumeInfo).getName();
Mockito.doReturn(0l).when(destVolumeInfo).getSize();
Mockito.doReturn(uuid).when(destVolumeInfo).getUuid();
DiskOfferingVO diskOffering = Mockito.spy(new DiskOfferingVO());
Mockito.doReturn(0l).when(diskOffering).getId();
Mockito.doReturn(diskOffering).when(diskOfferingDao).findById(0l);
DiskProfile diskProfile = Mockito.spy(new DiskProfile(destVolumeInfo, diskOffering, HypervisorType.KVM));
String templateUuid = Mockito.doReturn("templateUuid").when(kvmNonManagedStorageDataMotionStrategy).getTemplateUuid(0l);
CreateCommand rootImageProvisioningCommand = new CreateCommand(diskProfile, templateUuid, destStoragePool, true);
CreateAnswer createAnswer = Mockito.spy(new CreateAnswer(rootImageProvisioningCommand, "details"));
Mockito.doReturn(answerResult).when(createAnswer).getResult();
VolumeTO volumeTo = Mockito.mock(VolumeTO.class);
Mockito.doReturn(destPath).when(volumeTo).getName();
Mockito.doReturn(volumeTo).when(createAnswer).getVolume();
if (answerIsNull) {
Mockito.doReturn(null).when(agentManager).easySend(0l, rootImageProvisioningCommand);
} else {
Mockito.doReturn(createAnswer).when(agentManager).easySend(0l, rootImageProvisioningCommand);
}
String generatedDestPath = kvmNonManagedStorageDataMotionStrategy.generateDestPath(vmTO, srcVolume, new HostVO("sourceHostUuid"), destStoragePool, destVolumeInfo);
Assert.assertEquals(destPath + uuid, generatedDestPath);
}
@Test
public void shouldMigrateVolumeTest() {
StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());
HostVO destHost = new HostVO("guid");
StoragePoolVO destStoragePool = new StoragePoolVO();
StoragePoolType[] storagePoolTypes = StoragePoolType.values();
for (int i = 0; i < storagePoolTypes.length; i++) {
Mockito.doReturn(storagePoolTypes[i]).when(sourceStoragePool).getPoolType();
boolean result = kvmNonManagedStorageDataMotionStrategy.shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool);
if (storagePoolTypes[i] == StoragePoolType.Filesystem) {
Assert.assertTrue(result);
} else {
Assert.assertFalse(result);
}
}
}
}

View File

@ -18,67 +18,192 @@
*/
package org.apache.cloudstack.storage.motion;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ImageStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy;
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.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.image.store.ImageStoreImpl;
import org.apache.cloudstack.storage.volume.VolumeObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.MockitoAnnotations.initMocks;
import java.util.HashMap;
import java.util.Map;
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.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.PrimaryDataStoreImpl;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.image.store.ImageStoreImpl;
import org.apache.cloudstack.storage.volume.VolumeObject;
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.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.host.HostVO;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.ImageStore;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.VolumeVO;
@RunWith(MockitoJUnitRunner.class)
public class StorageSystemDataMotionStrategyTest {
@Mock
VolumeObject source;
@Mock
DataObject destination;
@Mock
PrimaryDataStore sourceStore;
@Mock
ImageStore destinationStore;
@Spy
@InjectMocks
DataMotionStrategy strategy = new StorageSystemDataMotionStrategy();
private StorageSystemDataMotionStrategy storageSystemDataMotionStrategy;
@Mock
PrimaryDataStoreDao _storagePoolDao;
private VolumeObject volumeObjectSource;
@Mock
private DataObject dataObjectDestination;
@Mock
private PrimaryDataStore primaryDataStoreSourceStore;
@Mock
private ImageStore destinationStore;
@Mock
private PrimaryDataStoreDao primaryDataStoreDao;
@Before public void setUp() throws Exception {
sourceStore = mock(PrimaryDataStoreImpl.class);
@Before
public void setUp() throws Exception {
primaryDataStoreSourceStore = mock(PrimaryDataStoreImpl.class);
destinationStore = mock(ImageStoreImpl.class);
source = mock(VolumeObject.class);
destination = mock(VolumeObject.class);
volumeObjectSource = mock(VolumeObject.class);
dataObjectDestination = mock(VolumeObject.class);
initMocks(strategy);
initMocks(storageSystemDataMotionStrategy);
}
@Test
public void cantHandleSecondary() {
doReturn(sourceStore).when(source).getDataStore();
doReturn(DataStoreRole.Primary).when(sourceStore).getRole();
doReturn(destinationStore).when(destination).getDataStore();
doReturn(primaryDataStoreSourceStore).when(volumeObjectSource).getDataStore();
doReturn(DataStoreRole.Primary).when(primaryDataStoreSourceStore).getRole();
doReturn(destinationStore).when(dataObjectDestination).getDataStore();
doReturn(DataStoreRole.Image).when((DataStore)destinationStore).getRole();
doReturn(sourceStore).when(source).getDataStore();
doReturn(destinationStore).when(destination).getDataStore();
doReturn(primaryDataStoreSourceStore).when(volumeObjectSource).getDataStore();
doReturn(destinationStore).when(dataObjectDestination).getDataStore();
StoragePoolVO storeVO = new StoragePoolVO();
doReturn(storeVO).when(_storagePoolDao).findById(0l);
doReturn(storeVO).when(primaryDataStoreDao).findById(0l);
assertTrue(strategy.canHandle(source,destination) == StrategyPriority.CANT_HANDLE);
assertTrue(storageSystemDataMotionStrategy.canHandle(volumeObjectSource, dataObjectDestination) == StrategyPriority.CANT_HANDLE);
}
}
@Test
public void internalCanHandleTestAllStoragePoolsAreManaged() {
configureAndTestInternalCanHandle(true, true, StrategyPriority.HIGHEST);
}
@Test
public void internalCanHandleTestFirstStoragePoolsIsManaged() {
configureAndTestInternalCanHandle(false, true, StrategyPriority.HIGHEST);
}
@Test
public void internalCanHandleTestSecondStoragePoolsIsManaged() {
configureAndTestInternalCanHandle(true, false, StrategyPriority.HIGHEST);
}
@Test
public void internalCanHandleTestNoStoragePoolsIsManaged() {
configureAndTestInternalCanHandle(false, false, StrategyPriority.CANT_HANDLE);
}
private void configureAndTestInternalCanHandle(boolean sPool0IsManaged, boolean sPool1IsManaged, StrategyPriority expectedStrategyPriority) {
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn(0l).when(volumeInfo).getPoolId();
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
Mockito.doReturn(1l).when(ds).getId();
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
volumeMap.put(volumeInfo, ds);
StoragePoolVO storagePool0 = Mockito.spy(new StoragePoolVO());
Mockito.doReturn(sPool0IsManaged).when(storagePool0).isManaged();
StoragePoolVO storagePool1 = Mockito.spy(new StoragePoolVO());
Mockito.doReturn(sPool1IsManaged).when(storagePool1).isManaged();
Mockito.doReturn(storagePool0).when(primaryDataStoreDao).findById(0l);
Mockito.doReturn(storagePool1).when(primaryDataStoreDao).findById(1l);
StrategyPriority strategyPriority = storageSystemDataMotionStrategy.internalCanHandle(volumeMap);
Assert.assertEquals(expectedStrategyPriority, strategyPriority);
}
@Test
public void isStoragePoolTypeOfFileTest() {
StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());
StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();
for (int i = 0; i < storagePoolTypeArray.length; i++) {
Mockito.doReturn(storagePoolTypeArray[i]).when(sourceStoragePool).getPoolType();
boolean result = storageSystemDataMotionStrategy.isStoragePoolTypeOfFile(sourceStoragePool);
if (sourceStoragePool.getPoolType() == StoragePoolType.Filesystem) {
Assert.assertTrue(result);
} else {
Assert.assertFalse(result);
}
}
}
@Test
public void generateDestPathTest() {
VolumeObject destVolumeInfo = Mockito.spy(new VolumeObject());
HostVO destHost = new HostVO("guid");
Mockito.doReturn("iScsiName").when(destVolumeInfo).get_iScsiName();
Mockito.doReturn(0l).when(destVolumeInfo).getPoolId();
Mockito.doReturn("expected").when(storageSystemDataMotionStrategy).connectHostToVolume(destHost, 0l, "iScsiName");
String expected = storageSystemDataMotionStrategy.generateDestPath(Mockito.mock(VirtualMachineTO.class), Mockito.mock(VolumeVO.class), destHost,
Mockito.mock(StoragePoolVO.class), destVolumeInfo);
Assert.assertEquals(expected, "expected");
Mockito.verify(storageSystemDataMotionStrategy).connectHostToVolume(destHost, 0l, "iScsiName");
}
@Test
public void configureMigrateDiskInfoTest() {
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
Mockito.doReturn("volume path").when(srcVolumeInfo).getPath();
MigrateCommand.MigrateDiskInfo migrateDiskInfo = storageSystemDataMotionStrategy.configureMigrateDiskInfo(srcVolumeInfo, "destPath");
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
Assert.assertEquals("destPath", migrateDiskInfo.getSourceText());
Assert.assertEquals("volume path", migrateDiskInfo.getSerialNumber());
}
@Test
public void setVolumePathTest() {
VolumeVO volume = new VolumeVO("name", 0l, 0l, 0l, 0l, 0l, "folder", "path", Storage.ProvisioningType.THIN, 0l, Volume.Type.ROOT);
String volumePath = "iScsiName";
volume.set_iScsiName(volumePath);
storageSystemDataMotionStrategy.setVolumePath(volume);
Assert.assertEquals(volumePath, volume.getPath());
}
@Test
public void shouldMigrateVolumeTest() {
StoragePoolVO sourceStoragePool = Mockito.spy(new StoragePoolVO());
HostVO destHost = new HostVO("guid");
StoragePoolVO destStoragePool = new StoragePoolVO();
StoragePoolType[] storagePoolTypes = StoragePoolType.values();
for (int i = 0; i < storagePoolTypes.length; i++) {
Mockito.doReturn(storagePoolTypes[i]).when(sourceStoragePool).getPoolType();
boolean result = storageSystemDataMotionStrategy.shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool);
Assert.assertTrue(result);
}
}
}

View File

@ -36,6 +36,36 @@ public class MigrateKVMAsync implements Callable<Domain> {
private boolean migrateStorage;
private boolean autoConvergence;
/**
* Do not pause the domain during migration. The domain's memory will be transferred to the destination host while the domain is running. The migration may never converge if the domain is changing its memory faster then it can be transferred. The domain can be manually paused anytime during migration using virDomainSuspend.
* @value 1
* @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
*/
private static final long VIR_MIGRATE_LIVE = 1L;
/**
* Migrate full disk images in addition to domain's memory. By default only non-shared non-readonly disk images are transferred. The VIR_MIGRATE_PARAM_MIGRATE_DISKS parameter can be used to specify which disks should be migrated. This flag and VIR_MIGRATE_NON_SHARED_INC are mutually exclusive.
* @value 64
* @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
*/
private static final long VIR_MIGRATE_NON_SHARED_DISK = 64L;
/**
* Compress migration data. The compression methods can be specified using VIR_MIGRATE_PARAM_COMPRESSION. A hypervisor default method will be used if this parameter is omitted. Individual compression methods can be tuned via their specific VIR_MIGRATE_PARAM_COMPRESSION_* parameters.
* @value 2048
* @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
*/
private static final long VIR_MIGRATE_COMPRESSED = 2048L;
/**
* Enable algorithms that ensure a live migration will eventually converge. This usually means the domain will be slowed down to make sure it does not change its memory faster than a hypervisor can transfer the changed memory to the destination host. VIR_MIGRATE_PARAM_AUTO_CONVERGE_* parameters can be used to tune the algorithm.
* @value 8192
* @see Libvirt <a href="https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainMigrateFlags">virDomainMigrateFlags</a> documentation
*/
private static final long VIR_MIGRATE_AUTO_CONVERGE = 8192L;
/**
* Libvirt 1.0.3 supports compression flag for migration.
*/
private static final int LIBVIRT_VERSION_SUPPORTS_MIGRATE_COMPRESSED = 1000003;
public MigrateKVMAsync(final LibvirtComputingResource libvirtComputingResource, final Domain dm, final Connect dconn, final String dxml,
final boolean migrateStorage, final boolean autoConvergence, final String vmName, final String destIp) {
this.libvirtComputingResource = libvirtComputingResource;
@ -51,19 +81,18 @@ public class MigrateKVMAsync implements Callable<Domain> {
@Override
public Domain call() throws LibvirtException {
long flags = 1 << 0;
long flags = VIR_MIGRATE_LIVE;
// set compression flag for migration, if libvirt version supports it
if (dconn.getLibVirVersion() >= 1000003) {
flags |= 1 << 11;
if (dconn.getLibVirVersion() >= LIBVIRT_VERSION_SUPPORTS_MIGRATE_COMPRESSED) {
flags += VIR_MIGRATE_COMPRESSED;
}
if (migrateStorage) {
flags |= 1 << 6;
flags += VIR_MIGRATE_NON_SHARED_DISK;
}
if (autoConvergence && dconn.getLibVirVersion() >= 1002003) {
flags |= 1 << 13;
flags += VIR_MIGRATE_AUTO_CONVERGE;
}
return dm.migrate(dconn, flags, dxml, vmName, "tcp:" + destIp, libvirtComputingResource.getMigrateSpeed());

View File

@ -59,7 +59,7 @@ public final class LibvirtCreateCommandWrapper extends CommandWrapper<CreateComm
vol = libvirtComputingResource.templateToPrimaryDownload(command.getTemplateUrl(), primaryPool, dskch.getPath());
} else {
baseVol = primaryPool.getPhysicalDisk(command.getTemplateUrl());
vol = storagePoolMgr.createDiskFromTemplate(baseVol, dskch.getPath(), dskch.getProvisioningType(), primaryPool, 0);
vol = storagePoolMgr.createDiskFromTemplate(baseVol, dskch.getPath(), dskch.getProvisioningType(), primaryPool, baseVol.getSize(), 0);
}
if (vol == null) {
return new Answer(command, false, " Can't create storage volume on storage pool");

View File

@ -51,6 +51,7 @@ import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainInfo.DomainState;
import org.libvirt.LibvirtException;
import org.libvirt.StorageVol;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
@ -61,7 +62,9 @@ import org.xml.sax.SAXException;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.MigrateAnswer;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
import com.cloud.hypervisor.kvm.resource.MigrateKVMAsync;
@ -91,6 +94,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
public Answer execute(final MigrateCommand command, final LibvirtComputingResource libvirtComputingResource) {
final String vmName = command.getVmName();
final String destinationUri = createMigrationURI(command.getDestinationIp(), libvirtComputingResource);
final List<MigrateDiskInfo> migrateDiskInfoList = command.getMigrateDiskInfoList();
String result = null;
@ -203,9 +207,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
destDomain = migrateThread.get(10, TimeUnit.SECONDS);
if (destDomain != null) {
for (final DiskDef disk : disks) {
libvirtComputingResource.cleanupDisk(disk);
}
deleteOrDisconnectDisksOnSourcePool(libvirtComputingResource, migrateDiskInfoList, disks);
}
} catch (final LibvirtException e) {
@ -279,6 +281,48 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
return new MigrateAnswer(command, result == null, result, null);
}
/**
* In case of a local file, it deletes the file on the source host/storage pool. Otherwise (for instance iScsi) it disconnects the disk on the source storage pool. </br>
* This method must be executed after a successful migration to a target storage pool, cleaning up the source storage.
*/
protected void deleteOrDisconnectDisksOnSourcePool(final LibvirtComputingResource libvirtComputingResource, final List<MigrateDiskInfo> migrateDiskInfoList,
List<DiskDef> disks) {
for (DiskDef disk : disks) {
MigrateDiskInfo migrateDiskInfo = searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, disk);
if (migrateDiskInfo != null && migrateDiskInfo.isSourceDiskOnStorageFileSystem()) {
deleteLocalVolume(disk.getDiskPath());
} else {
libvirtComputingResource.cleanupDisk(disk);
}
}
}
/**
* Deletes the local volume from the storage pool.
*/
protected void deleteLocalVolume(String localPath) {
try {
Connect conn = LibvirtConnection.getConnection();
StorageVol storageVolLookupByPath = conn.storageVolLookupByPath(localPath);
storageVolLookupByPath.delete(0);
} catch (LibvirtException e) {
s_logger.error(String.format("Cannot delete local volume [%s] due to: %s", localPath, e));
}
}
/**
* Searches for a {@link MigrateDiskInfo} with the path matching the {@link DiskDef} path.
*/
protected MigrateDiskInfo searchDiskDefOnMigrateDiskInfoList(List<MigrateDiskInfo> migrateDiskInfoList, DiskDef disk) {
for (MigrateDiskInfo migrateDiskInfo : migrateDiskInfoList) {
if (StringUtils.contains(disk.getDiskPath(), migrateDiskInfo.getSerialNumber())) {
return migrateDiskInfo;
}
}
s_logger.debug(String.format("Cannot find Disk [uuid: %s] on the list of disks to be migrated", disk.getDiskPath()));
return null;
}
/**
* This function assumes an qemu machine description containing a single graphics element like
* <graphics type='vnc' port='5900' autoport='yes' listen='10.10.10.1'>
@ -338,7 +382,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
String path = getPathFromSourceText(migrateStorage.keySet(), sourceText);
if (path != null) {
MigrateCommand.MigrateDiskInfo migrateDiskInfo = migrateStorage.remove(path);
MigrateCommand.MigrateDiskInfo migrateDiskInfo = migrateStorage.get(path);
NamedNodeMap diskNodeAttributes = diskNode.getAttributes();
Node diskNodeAttribute = diskNodeAttributes.getNamedItem("type");
@ -377,10 +421,6 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo
}
}
if (!migrateStorage.isEmpty()) {
throw new CloudRuntimeException("Disk info was passed into LibvirtMigrateCommandWrapper.replaceStorage that was not used.");
}
return getXml(doc);
}

View File

@ -349,8 +349,7 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
String search4 = "-lun-";
if (!localPath.contains(search3)) {
// this volume doesn't below to this adaptor, so just return true
return true;
return false;
}
int index = localPath.indexOf(search2);

View File

@ -42,6 +42,7 @@ import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageLayer;
import com.cloud.storage.Volume;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
import org.reflections.Reflections;
@ -138,20 +139,26 @@ public class KVMStoragePoolManager {
List<DiskTO> disks = Arrays.asList(vmSpec.getDisks());
for (DiskTO disk : disks) {
if (disk.getType() != Volume.Type.ISO) {
VolumeObjectTO vol = (VolumeObjectTO)disk.getData();
PrimaryDataStoreTO store = (PrimaryDataStoreTO)vol.getDataStore();
KVMStoragePool pool = getStoragePool(store.getPoolType(), store.getUuid());
if (disk.getType() == Volume.Type.ISO) {
result = true;
continue;
}
StorageAdaptor adaptor = getStorageAdaptor(pool.getType());
VolumeObjectTO vol = (VolumeObjectTO)disk.getData();
PrimaryDataStoreTO store = (PrimaryDataStoreTO)vol.getDataStore();
if (!store.isManaged() && VirtualMachine.State.Migrating.equals(vmSpec.getState())) {
result = true;
continue;
}
result = adaptor.connectPhysicalDisk(vol.getPath(), pool, disk.getDetails());
KVMStoragePool pool = getStoragePool(store.getPoolType(), store.getUuid());
StorageAdaptor adaptor = getStorageAdaptor(pool.getType());
if (!result) {
s_logger.error("Failed to connect disks via vm spec for vm: " + vmName + " volume:" + vol.toString());
result = adaptor.connectPhysicalDisk(vol.getPath(), pool, disk.getDetails());
return result;
}
if (!result) {
s_logger.error("Failed to connect disks via vm spec for vm: " + vmName + " volume:" + vol.toString());
return result;
}
}

View File

@ -1765,8 +1765,7 @@ public class LibvirtComputingResourceTest {
when(poolManager.getStoragePool(pool.getType(), pool.getUuid())).thenReturn(primary);
when(primary.getPhysicalDisk(command.getTemplateUrl())).thenReturn(baseVol);
when(poolManager.createDiskFromTemplate(baseVol,
diskCharacteristics.getPath(), diskCharacteristics.getProvisioningType(), primary, 0)).thenReturn(vol);
when(poolManager.createDiskFromTemplate(baseVol, diskCharacteristics.getPath(), diskCharacteristics.getProvisioningType(), primary, baseVol.getSize(), 0)).thenReturn(vol);
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
assertNotNull(wrapper);

View File

@ -21,11 +21,31 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libvirt.Connect;
import org.libvirt.StorageVol;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DiskType;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.DriverType;
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo.Source;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.hypervisor.kvm.resource.LibvirtConnection;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
import com.cloud.utils.exception.CloudRuntimeException;
@RunWith(PowerMockRunner.class)
@PrepareForTest({LibvirtConnection.class, LibvirtMigrateCommandWrapper.class})
public class LibvirtMigrateCommandWrapperTest {
String fullfile =
"<domain type='kvm' id='4'>\n" +
@ -252,11 +272,12 @@ public class LibvirtMigrateCommandWrapperTest {
" </devices>\n" +
"</domain>";
LibvirtMigrateCommandWrapper libvirtMigrateCmdWrapper = new LibvirtMigrateCommandWrapper();
@Test
public void testReplaceIpForVNCInDescFile() {
final String targetIp = "192.168.22.21";
final LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
final String result = lw.replaceIpForVNCInDescFile(fullfile, targetIp);
final String result = libvirtMigrateCmdWrapper.replaceIpForVNCInDescFile(fullfile, targetIp);
assertTrue("transformation does not live up to expectation:\n" + result, targetfile.equals(result));
}
@ -279,8 +300,7 @@ public class LibvirtMigrateCommandWrapperTest {
" </devices>" +
"</domain>";
final String targetIp = "10.10.10.10";
final LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
final String result = lw.replaceIpForVNCInDescFile(xmlDesc, targetIp);
final String result = libvirtMigrateCmdWrapper.replaceIpForVNCInDescFile(xmlDesc, targetIp);
assertTrue("transformation does not live up to expectation:\n" + result, expectedXmlDesc.equals(result));
}
@ -303,26 +323,116 @@ public class LibvirtMigrateCommandWrapperTest {
" </devices>" +
"</domain>";
final String targetIp = "localhost.localdomain";
final LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
final String result = lw.replaceIpForVNCInDescFile(xmlDesc, targetIp);
final String result = libvirtMigrateCmdWrapper.replaceIpForVNCInDescFile(xmlDesc, targetIp);
assertTrue("transformation does not live up to expectation:\n" + result, expectedXmlDesc.equals(result));
}
@Test
public void testMigrationUri() {
final String ip = "10.1.1.1";
LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
LibvirtComputingResource lcr = new LibvirtComputingResource();
if (lcr.isHostSecured()) {
assertEquals(lw.createMigrationURI(ip, lcr), String.format("qemu+tls://%s/system", ip));
assertEquals(libvirtMigrateCmdWrapper.createMigrationURI(ip, lcr), String.format("qemu+tls://%s/system", ip));
} else {
assertEquals(lw.createMigrationURI(ip, lcr), String.format("qemu+tcp://%s/system", ip));
assertEquals(libvirtMigrateCmdWrapper.createMigrationURI(ip, lcr), String.format("qemu+tcp://%s/system", ip));
}
}
@Test(expected = CloudRuntimeException.class)
public void testMigrationUriException() {
LibvirtMigrateCommandWrapper lw = new LibvirtMigrateCommandWrapper();
lw.createMigrationURI(null, new LibvirtComputingResource());
libvirtMigrateCmdWrapper.createMigrationURI(null, new LibvirtComputingResource());
}
@Test
public void deleteLocalVolumeTest() throws Exception {
PowerMockito.mockStatic(LibvirtConnection.class);
Connect conn = Mockito.mock(Connect.class);
PowerMockito.doReturn(conn).when(LibvirtConnection.class, "getConnection");
StorageVol storageVolLookupByPath = Mockito.mock(StorageVol.class);
Mockito.when(conn.storageVolLookupByPath("localPath")).thenReturn(storageVolLookupByPath);
libvirtMigrateCmdWrapper.deleteLocalVolume("localPath");
PowerMockito.verifyStatic(Mockito.times(1));
LibvirtConnection.getConnection();
InOrder inOrder = Mockito.inOrder(conn, storageVolLookupByPath);
inOrder.verify(conn, Mockito.times(1)).storageVolLookupByPath("localPath");
inOrder.verify(storageVolLookupByPath, Mockito.times(1)).delete(0);
}
@Test
public void searchDiskDefOnMigrateDiskInfoListTest() {
configureAndVerifyTestSearchDiskDefOnMigrateDiskInfoList("f3d49ecc-870c-475a-89fa-fd0124420a9b", "/var/lib/libvirt/images/f3d49ecc-870c-475a-89fa-fd0124420a9b", false);
}
@Test
public void searchDiskDefOnMigrateDiskInfoListTestExpectNull() {
configureAndVerifyTestSearchDiskDefOnMigrateDiskInfoList("f3d49ecc-870c-475a-89fa-fd0124420a9b", "/var/lib/libvirt/images/f3d49ecc-870c-89fa-fd0124420a9b", true);
}
private void configureAndVerifyTestSearchDiskDefOnMigrateDiskInfoList(String serialNumber, String diskPath, boolean isExpectedDiskInfoNull) {
MigrateDiskInfo migrateDiskInfo = new MigrateDiskInfo(serialNumber, DiskType.FILE, DriverType.QCOW2, Source.FILE, "sourceText");
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
migrateDiskInfoList.add(migrateDiskInfo);
DiskDef disk = new DiskDef();
disk.setDiskPath(diskPath);
MigrateDiskInfo returnedMigrateDiskInfo = libvirtMigrateCmdWrapper.searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, disk);
if (isExpectedDiskInfoNull)
Assert.assertEquals(null, returnedMigrateDiskInfo);
else
Assert.assertEquals(migrateDiskInfo, returnedMigrateDiskInfo);
}
@Test
public void deleteOrDisconnectDisksOnSourcePoolTest() {
LibvirtMigrateCommandWrapper spyLibvirtMigrateCmdWrapper = PowerMockito.spy(libvirtMigrateCmdWrapper);
Mockito.doNothing().when(spyLibvirtMigrateCmdWrapper).deleteLocalVolume("volPath");
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<>();
MigrateDiskInfo migrateDiskInfo0 = createMigrateDiskInfo(true);
MigrateDiskInfo migrateDiskInfo2 = createMigrateDiskInfo(false);
List<DiskDef> disks = new ArrayList<>();
DiskDef diskDef0 = new DiskDef();
DiskDef diskDef1 = new DiskDef();
DiskDef diskDef2 = new DiskDef();
diskDef0.setDiskPath("volPath");
disks.add(diskDef0);
disks.add(diskDef1);
disks.add(diskDef2);
LibvirtComputingResource libvirtComputingResource = Mockito.spy(new LibvirtComputingResource());
Mockito.doReturn(true).when(libvirtComputingResource).cleanupDisk(diskDef1);
Mockito.doReturn(migrateDiskInfo0).when(spyLibvirtMigrateCmdWrapper).searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, diskDef0);
Mockito.doReturn(null).when(spyLibvirtMigrateCmdWrapper).searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, diskDef1);
Mockito.doReturn(migrateDiskInfo2).when(spyLibvirtMigrateCmdWrapper).searchDiskDefOnMigrateDiskInfoList(migrateDiskInfoList, diskDef2);
spyLibvirtMigrateCmdWrapper.deleteOrDisconnectDisksOnSourcePool(libvirtComputingResource, migrateDiskInfoList, disks);
InOrder inOrder = Mockito.inOrder(spyLibvirtMigrateCmdWrapper, libvirtComputingResource);
inOrderVerifyDeleteOrDisconnect(inOrder, spyLibvirtMigrateCmdWrapper, libvirtComputingResource, migrateDiskInfoList, diskDef0, 1, 0);
inOrderVerifyDeleteOrDisconnect(inOrder, spyLibvirtMigrateCmdWrapper, libvirtComputingResource, migrateDiskInfoList, diskDef1, 0, 1);
inOrderVerifyDeleteOrDisconnect(inOrder, spyLibvirtMigrateCmdWrapper, libvirtComputingResource, migrateDiskInfoList, diskDef2, 0, 1);
}
private MigrateDiskInfo createMigrateDiskInfo(boolean isSourceDiskOnStorageFileSystem) {
MigrateDiskInfo migrateDiskInfo = new MigrateDiskInfo("serialNumber", DiskType.FILE, DriverType.QCOW2, Source.FILE, "sourceText");
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isSourceDiskOnStorageFileSystem);
return migrateDiskInfo;
}
private void inOrderVerifyDeleteOrDisconnect(InOrder inOrder, LibvirtMigrateCommandWrapper lw, LibvirtComputingResource virtResource, List<MigrateDiskInfo> diskInfoList,
DiskDef disk, int timesDelete, int timesCleanup) {
inOrder.verify(lw).searchDiskDefOnMigrateDiskInfoList(diskInfoList, disk);
inOrder.verify(lw, Mockito.times(timesDelete)).deleteLocalVolume("volPath");
inOrder.verify(virtResource, Mockito.times(timesCleanup)).cleanupDisk(disk);
}
}

View File

@ -206,7 +206,9 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri
result.setResult(errMsg);
}
callback.complete(result);
if (callback != null) {
callback.complete(result);
}
}
@Override

View File

@ -188,6 +188,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
to.setConfigDriveLabel(vmProfile.getConfigDriveLabel());
to.setConfigDriveIsoRootFolder(vmProfile.getConfigDriveIsoRootFolder());
to.setConfigDriveIsoFile(vmProfile.getConfigDriveIsoFile());
to.setState(vm.getState());
return to;
}