mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Support online migration of a virtual disk on XenServer from non-managed storage to managed storage
This commit is contained in:
parent
46c56eaaf9
commit
3db33b7385
@ -97,10 +97,18 @@ public class MigrateVolumeCommand extends Command {
|
|||||||
return destData;
|
return destData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSrcDetails(Map<String, String> details) {
|
||||||
|
srcDetails = details;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getSrcDetails() {
|
public Map<String, String> getSrcDetails() {
|
||||||
return srcDetails;
|
return srcDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDestDetails(Map<String, String> details) {
|
||||||
|
destDetails = details;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getDestDetails() {
|
public Map<String, String> getDestDetails() {
|
||||||
return destDetails;
|
return destDetails;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,9 +25,12 @@ import com.cloud.host.Host;
|
|||||||
import com.cloud.storage.StoragePool;
|
import com.cloud.storage.StoragePool;
|
||||||
|
|
||||||
public interface PrimaryDataStoreDriver extends DataStoreDriver {
|
public interface PrimaryDataStoreDriver extends DataStoreDriver {
|
||||||
|
enum QualityOfServiceState { MIGRATION, NO_MIGRATION }
|
||||||
|
|
||||||
String BASIC_CREATE = "basicCreate";
|
String BASIC_CREATE = "basicCreate";
|
||||||
String BASIC_DELETE = "basicDelete";
|
String BASIC_DELETE = "basicDelete";
|
||||||
String BASIC_DELETE_FAILURE = "basicDeleteFailure";
|
String BASIC_DELETE_FAILURE = "basicDeleteFailure";
|
||||||
|
String BASIC_DELETE_BY_FOLDER = "basicDeleteByFolder";
|
||||||
String BASIC_GRANT_ACCESS = "basicGrantAccess";
|
String BASIC_GRANT_ACCESS = "basicGrantAccess";
|
||||||
String BASIC_REVOKE_ACCESS = "basicRevokeAccess";
|
String BASIC_REVOKE_ACCESS = "basicRevokeAccess";
|
||||||
String BASIC_IQN = "basicIqn";
|
String BASIC_IQN = "basicIqn";
|
||||||
@ -67,4 +70,6 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver {
|
|||||||
void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback);
|
void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback);
|
||||||
|
|
||||||
void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback<CommandResult> callback);
|
void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback<CommandResult> callback);
|
||||||
|
|
||||||
|
void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import com.cloud.storage.Snapshot;
|
|||||||
import com.cloud.storage.SnapshotVO;
|
import com.cloud.storage.SnapshotVO;
|
||||||
import com.cloud.storage.Storage.ImageFormat;
|
import com.cloud.storage.Storage.ImageFormat;
|
||||||
import com.cloud.storage.StorageManager;
|
import com.cloud.storage.StorageManager;
|
||||||
|
import com.cloud.storage.StoragePool;
|
||||||
import com.cloud.storage.VMTemplateVO;
|
import com.cloud.storage.VMTemplateVO;
|
||||||
import com.cloud.storage.VolumeDetailVO;
|
import com.cloud.storage.VolumeDetailVO;
|
||||||
import com.cloud.storage.Volume;
|
import com.cloud.storage.Volume;
|
||||||
@ -85,8 +86,10 @@ import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
|||||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
|
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
|
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
|
||||||
|
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
|
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
|
||||||
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.StorageAction;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
|
import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
|
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
|
||||||
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
|
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
|
||||||
@ -295,7 +298,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
|
VolumeInfo srcVolumeInfo = (VolumeInfo)srcData;
|
||||||
TemplateInfo destTemplateInfo = (TemplateInfo)destData;
|
TemplateInfo destTemplateInfo = (TemplateInfo)destData;
|
||||||
|
|
||||||
handleCreateTemplateFromVolume(srcVolumeInfo, destTemplateInfo, callback);
|
handleCreateTemplateFromManagedVolume(srcVolumeInfo, destTemplateInfo, callback);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
handleError(OPERATION_NOT_SUPPORTED, callback);
|
handleError(OPERATION_NOT_SUPPORTED, callback);
|
||||||
@ -309,7 +312,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
if (canHandleSrc && (destData instanceof TemplateInfo || destData instanceof SnapshotInfo) &&
|
if (canHandleSrc && (destData instanceof TemplateInfo || destData instanceof SnapshotInfo) &&
|
||||||
(destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
|
(destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache)) {
|
||||||
handleCopyDataToSecondaryStorage(srcSnapshotInfo, destData, callback);
|
handleCopyAsyncToSecondaryStorage(srcSnapshotInfo, destData, callback);
|
||||||
} else if (destData instanceof VolumeInfo) {
|
} else if (destData instanceof VolumeInfo) {
|
||||||
handleCopyAsyncForSnapshotToVolume(srcSnapshotInfo, (VolumeInfo)destData, callback);
|
handleCopyAsyncForSnapshotToVolume(srcSnapshotInfo, (VolumeInfo)destData, callback);
|
||||||
} else {
|
} else {
|
||||||
@ -319,24 +322,26 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
private void handleCopyAsyncForSnapshotToVolume(SnapshotInfo srcSnapshotInfo, VolumeInfo destVolumeInfo,
|
private void handleCopyAsyncForSnapshotToVolume(SnapshotInfo srcSnapshotInfo, VolumeInfo destVolumeInfo,
|
||||||
AsyncCompletionCallback<CopyCommandResult> callback) {
|
AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
|
boolean canHandleSrc = canHandle(srcSnapshotInfo);
|
||||||
boolean canHandleDest = canHandle(destVolumeInfo);
|
boolean canHandleDest = canHandle(destVolumeInfo);
|
||||||
|
|
||||||
if (!canHandleDest) {
|
if (canHandleSrc && canHandleDest) {
|
||||||
|
if (srcSnapshotInfo.getDataStore().getId() == destVolumeInfo.getDataStore().getId()) {
|
||||||
|
handleCreateManagedVolumeFromManagedSnapshot(srcSnapshotInfo, destVolumeInfo, callback);
|
||||||
|
} else {
|
||||||
|
String errMsg = "To perform this operation, the source and destination primary storages must be the same.";
|
||||||
|
|
||||||
|
handleError(errMsg, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!canHandleSrc && !canHandleDest) {
|
||||||
handleError(OPERATION_NOT_SUPPORTED, callback);
|
handleError(OPERATION_NOT_SUPPORTED, callback);
|
||||||
}
|
}
|
||||||
|
else if (canHandleSrc) {
|
||||||
boolean canHandleSrc = canHandle(srcSnapshotInfo);
|
handleCreateNonManagedVolumeFromManagedSnapshot(srcSnapshotInfo, destVolumeInfo, callback);
|
||||||
|
|
||||||
if (!canHandleSrc) {
|
|
||||||
handleCreateVolumeFromSnapshotOnSecondaryStorage(srcSnapshotInfo, destVolumeInfo, callback);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (srcSnapshotInfo.getDataStore().getId() == destVolumeInfo.getDataStore().getId()) {
|
handleCreateManagedVolumeFromNonManagedSnapshot(srcSnapshotInfo, destVolumeInfo, callback);
|
||||||
handleCreateVolumeFromSnapshotBothOnStorageSystem(srcSnapshotInfo, destVolumeInfo, callback);
|
|
||||||
} else {
|
|
||||||
String errMsg = "To perform this operation, the source and destination primary storages must be the same.";
|
|
||||||
|
|
||||||
handleError(errMsg, callback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +441,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
hostVO = getHost(srcVolumeInfo.getDataCenterId(), hypervisorType, false);
|
hostVO = getHost(srcVolumeInfo.getDataCenterId(), hypervisorType, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
volumePath = copyVolumeToSecondaryStorage(srcVolumeInfo, destVolumeInfo, hostVO,
|
volumePath = copyManagedVolumeToSecondaryStorage(srcVolumeInfo, destVolumeInfo, hostVO,
|
||||||
"Unable to copy the volume from managed storage to secondary storage");
|
"Unable to copy the volume from managed storage to secondary storage");
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
@ -503,7 +508,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
setCertainVolumeValuesNull(destVolumeInfo.getId());
|
setCertainVolumeValuesNull(destVolumeInfo.getId());
|
||||||
|
|
||||||
// migrate the volume via the hypervisor
|
// migrate the volume via the hypervisor
|
||||||
String path = migrateVolume(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from managed storage to non-managed storage");
|
String path = migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from managed storage to non-managed storage");
|
||||||
|
|
||||||
updateVolumePath(destVolumeInfo.getId(), path);
|
updateVolumePath(destVolumeInfo.getId(), path);
|
||||||
}
|
}
|
||||||
@ -625,48 +630,17 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
try {
|
try {
|
||||||
HypervisorType hypervisorType = srcVolumeInfo.getHypervisorType();
|
HypervisorType hypervisorType = srcVolumeInfo.getHypervisorType();
|
||||||
|
|
||||||
if (!HypervisorType.KVM.equals(hypervisorType)) {
|
if (!HypervisorType.XenServer.equals(hypervisorType) && !HypervisorType.KVM.equals(hypervisorType)) {
|
||||||
throw new CloudRuntimeException("Currently, only the KVM hypervisor type is supported for the migration of a volume " +
|
throw new CloudRuntimeException("Currently, only the XenServer and KVM hypervisor types are supported for the migration of a volume " +
|
||||||
"from non-managed storage to managed storage.");
|
"from non-managed storage to managed storage.");
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualMachine vm = srcVolumeInfo.getAttachedVM();
|
if (HypervisorType.XenServer.equals(hypervisorType)) {
|
||||||
|
handleVolumeMigrationForXenServer(srcVolumeInfo, destVolumeInfo);
|
||||||
if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
|
|
||||||
throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage is attached to " +
|
|
||||||
"a VM, the VM must be in the Stopped state.");
|
|
||||||
}
|
|
||||||
|
|
||||||
destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
|
|
||||||
|
|
||||||
VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
|
|
||||||
|
|
||||||
volumeVO.setPath(volumeVO.get_iScsiName());
|
|
||||||
|
|
||||||
_volumeDao.update(volumeVO.getId(), volumeVO);
|
|
||||||
|
|
||||||
destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
|
|
||||||
|
|
||||||
long srcStoragePoolId = srcVolumeInfo.getPoolId();
|
|
||||||
StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(srcStoragePoolId);
|
|
||||||
|
|
||||||
HostVO hostVO;
|
|
||||||
|
|
||||||
if (srcStoragePoolVO.getClusterId() != null) {
|
|
||||||
hostVO = getHostInCluster(srcStoragePoolVO.getClusterId());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
hostVO = getHost(destVolumeInfo.getDataCenterId(), hypervisorType, false);
|
handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrate the volume via the hypervisor
|
|
||||||
migrateVolume(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage");
|
|
||||||
|
|
||||||
volumeVO = _volumeDao.findById(destVolumeInfo.getId());
|
|
||||||
|
|
||||||
volumeVO.setFormat(ImageFormat.QCOW2);
|
|
||||||
|
|
||||||
_volumeDao.update(volumeVO.getId(), volumeVO);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeMigrationFromNonManagedStorageToManagedStorage': " +
|
errMsg = "Migration operation failed in 'StorageSystemDataMotionStrategy.handleVolumeMigrationFromNonManagedStorageToManagedStorage': " +
|
||||||
@ -696,6 +670,155 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleVolumeMigrationForXenServer(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) {
|
||||||
|
VirtualMachine vm = srcVolumeInfo.getAttachedVM();
|
||||||
|
|
||||||
|
if (vm == null || vm.getState() != VirtualMachine.State.Running) {
|
||||||
|
throw new CloudRuntimeException("Currently, a volume to migrate from non-managed storage to managed storage on XenServer must be attached to " +
|
||||||
|
"a VM in the Running state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
|
||||||
|
|
||||||
|
destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
|
HostVO hostVO = _hostDao.findById(vm.getHostId());
|
||||||
|
|
||||||
|
_volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
|
||||||
|
|
||||||
|
String value = _configDao.getValue(Config.MigrateWait.key());
|
||||||
|
int waitInterval = NumbersUtil.parseInt(value, Integer.parseInt(Config.MigrateWait.getDefaultValue()));
|
||||||
|
|
||||||
|
StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destVolumeInfo.getDataStore().getId(), DataStoreRole.Primary);
|
||||||
|
|
||||||
|
MigrateVolumeCommand command = new MigrateVolumeCommand(srcVolumeInfo.getId(), srcVolumeInfo.getPath(), destPool, srcVolumeInfo.getAttachedVmName(),
|
||||||
|
srcVolumeInfo.getVolumeType(), waitInterval);
|
||||||
|
|
||||||
|
Map<String, String> details = new HashMap<>();
|
||||||
|
|
||||||
|
details.put(DiskTO.MANAGED, Boolean.TRUE.toString());
|
||||||
|
details.put(DiskTO.IQN, destVolumeInfo.get_iScsiName());
|
||||||
|
details.put(DiskTO.STORAGE_HOST, destPool.getHostAddress());
|
||||||
|
|
||||||
|
command.setDestDetails(details);
|
||||||
|
|
||||||
|
EndPoint ep = selector.select(srcVolumeInfo, StorageAction.MIGRATEVOLUME);
|
||||||
|
|
||||||
|
Answer answer;
|
||||||
|
|
||||||
|
if (ep == null) {
|
||||||
|
String errMsg = "No remote endpoint to send command to; check if host or SSVM is down";
|
||||||
|
|
||||||
|
LOGGER.error(errMsg);
|
||||||
|
|
||||||
|
answer = new Answer(command, false, errMsg);
|
||||||
|
} else {
|
||||||
|
answer = ep.sendMessage(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
|
|
||||||
|
if (answer == null || !answer.getResult()) {
|
||||||
|
handleFailedVolumeMigration(srcVolumeInfo, destVolumeInfo, hostVO);
|
||||||
|
|
||||||
|
throw new CloudRuntimeException("Failed to migrate volume with ID " + srcVolumeInfo.getId() + " to storage pool with ID " + destPool.getId());
|
||||||
|
} else {
|
||||||
|
handleSuccessfulVolumeMigration(srcVolumeInfo, destPool, (MigrateVolumeAnswer)answer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSuccessfulVolumeMigration(VolumeInfo srcVolumeInfo, StoragePool destPool, MigrateVolumeAnswer migrateVolumeAnswer) {
|
||||||
|
VolumeVO volumeVO = _volumeDao.findById(srcVolumeInfo.getId());
|
||||||
|
|
||||||
|
volumeVO.setPath(migrateVolumeAnswer.getVolumePath());
|
||||||
|
|
||||||
|
String chainInfo = migrateVolumeAnswer.getVolumeChainInfo();
|
||||||
|
|
||||||
|
if (chainInfo != null) {
|
||||||
|
volumeVO.setChainInfo(chainInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeVO.setPodId(destPool.getPodId());
|
||||||
|
volumeVO.setPoolId(destPool.getId());
|
||||||
|
volumeVO.setLastPoolId(srcVolumeInfo.getPoolId());
|
||||||
|
|
||||||
|
_volumeDao.update(srcVolumeInfo.getId(), volumeVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFailedVolumeMigration(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO) {
|
||||||
|
try {
|
||||||
|
_volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LOGGER.warn("Failed to revoke access to the volume with the following ID: " + destVolumeInfo.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
VolumeDetailVO volumeDetailVO = new VolumeDetailVO(destVolumeInfo.getId(), PrimaryDataStoreDriver.BASIC_DELETE_BY_FOLDER,
|
||||||
|
Boolean.TRUE.toString(), false);
|
||||||
|
|
||||||
|
volumeDetailsDao.persist(volumeDetailVO);
|
||||||
|
|
||||||
|
destVolumeInfo.getDataStore().getDriver().deleteAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
|
||||||
|
|
||||||
|
volumeDetailsDao.removeDetails(srcVolumeInfo.getId());
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LOGGER.warn(ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
VolumeVO volumeVO = _volumeDao.findById(srcVolumeInfo.getId());
|
||||||
|
|
||||||
|
volumeVO.setPoolId(srcVolumeInfo.getPoolId());
|
||||||
|
volumeVO.setLastPoolId(srcVolumeInfo.getLastPoolId());
|
||||||
|
volumeVO.setFolder(srcVolumeInfo.getFolder());
|
||||||
|
volumeVO.set_iScsiName(srcVolumeInfo.get_iScsiName());
|
||||||
|
|
||||||
|
_volumeDao.update(srcVolumeInfo.getId(), volumeVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVolumeMigrationForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) {
|
||||||
|
VirtualMachine vm = srcVolumeInfo.getAttachedVM();
|
||||||
|
|
||||||
|
if (vm != null && vm.getState() != VirtualMachine.State.Stopped) {
|
||||||
|
throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage on KVM is attached to " +
|
||||||
|
"a VM, the VM must be in the Stopped state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null);
|
||||||
|
|
||||||
|
VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
|
||||||
|
|
||||||
|
volumeVO.setPath(volumeVO.get_iScsiName());
|
||||||
|
|
||||||
|
_volumeDao.update(volumeVO.getId(), volumeVO);
|
||||||
|
|
||||||
|
destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore());
|
||||||
|
|
||||||
|
long srcStoragePoolId = srcVolumeInfo.getPoolId();
|
||||||
|
StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(srcStoragePoolId);
|
||||||
|
|
||||||
|
HostVO hostVO;
|
||||||
|
|
||||||
|
if (srcStoragePoolVO.getClusterId() != null) {
|
||||||
|
hostVO = getHostInCluster(srcStoragePoolVO.getClusterId());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hostVO = getHost(destVolumeInfo.getDataCenterId(), HypervisorType.KVM, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrate the volume via the hypervisor
|
||||||
|
migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage");
|
||||||
|
|
||||||
|
volumeVO = _volumeDao.findById(destVolumeInfo.getId());
|
||||||
|
|
||||||
|
volumeVO.setFormat(ImageFormat.QCOW2);
|
||||||
|
|
||||||
|
_volumeDao.update(volumeVO.getId(), volumeVO);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is responsible for copying a snapshot from managed storage to secondary storage. This is used in the following two cases:
|
* This function is responsible for copying a snapshot from managed storage to secondary storage. This is used in the following two cases:
|
||||||
* 1) When creating a template from a snapshot
|
* 1) When creating a template from a snapshot
|
||||||
@ -705,7 +828,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
* @param destData destination (can be template or snapshot)
|
* @param destData destination (can be template or snapshot)
|
||||||
* @param callback callback for async
|
* @param callback callback for async
|
||||||
*/
|
*/
|
||||||
private void handleCopyDataToSecondaryStorage(SnapshotInfo snapshotInfo, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
|
private void handleCopyAsyncToSecondaryStorage(SnapshotInfo snapshotInfo, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
String errMsg = null;
|
String errMsg = null;
|
||||||
CopyCmdAnswer copyCmdAnswer = null;
|
CopyCmdAnswer copyCmdAnswer = null;
|
||||||
boolean usingBackendSnapshot = false;
|
boolean usingBackendSnapshot = false;
|
||||||
@ -786,13 +909,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
disconnectHostFromVolume(hostVO, srcDataStore.getId(), iqn);
|
disconnectHostFromVolume(hostVO, srcDataStore.getId(), iqn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
|
verifyCopyCmdAnswer(copyCmdAnswer, snapshotInfo);
|
||||||
if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
|
|
||||||
throw new CloudRuntimeException(copyCmdAnswer.getDetails());
|
|
||||||
} else {
|
|
||||||
throw new CloudRuntimeException("Unable to create volume from snapshot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vmdk = copyCmdAnswer.getNewData().getPath();
|
vmdk = copyCmdAnswer.getNewData().getPath();
|
||||||
uuid = UUID.randomUUID().toString();
|
uuid = UUID.randomUUID().toString();
|
||||||
@ -829,9 +946,10 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
|
copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
|
||||||
|
|
||||||
if (!copyCmdAnswer.getResult()) {
|
if (!copyCmdAnswer.getResult()) {
|
||||||
// We were not able to copy. Handle it.
|
|
||||||
errMsg = copyCmdAnswer.getDetails();
|
errMsg = copyCmdAnswer.getDetails();
|
||||||
|
|
||||||
|
LOGGER.warn(errMsg);
|
||||||
|
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -925,14 +1043,155 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCreateNonManagedVolumeFromManagedSnapshot(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
|
||||||
|
AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
|
if (!HypervisorType.XenServer.equals(snapshotInfo.getHypervisorType())) {
|
||||||
|
String errMsg = "Creating a volume on non-managed storage from a snapshot on managed storage is currently only supported with XenServer.";
|
||||||
|
|
||||||
|
handleError(errMsg, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
long volumeStoragePoolId = volumeInfo.getDataStore().getId();
|
||||||
|
StoragePoolVO volumeStoragePoolVO = _storagePoolDao.findById(volumeStoragePoolId);
|
||||||
|
|
||||||
|
if (volumeStoragePoolVO.getClusterId() == null) {
|
||||||
|
String errMsg = "To create a non-managed volume from a managed snapshot, the destination storage pool must be cluster scoped.";
|
||||||
|
|
||||||
|
handleError(errMsg, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
String errMsg = null;
|
||||||
|
CopyCmdAnswer copyCmdAnswer = null;
|
||||||
|
|
||||||
|
boolean usingBackendSnapshot = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
snapshotInfo.processEvent(Event.CopyingRequested);
|
||||||
|
|
||||||
|
usingBackendSnapshot = usingBackendSnapshotFor(snapshotInfo);
|
||||||
|
|
||||||
|
if (usingBackendSnapshot) {
|
||||||
|
boolean computeClusterSupportsVolumeClone = clusterDao.getSupportsResigning(volumeStoragePoolVO.getClusterId());
|
||||||
|
|
||||||
|
if (!computeClusterSupportsVolumeClone) {
|
||||||
|
String noSupportForResignErrMsg = "Unable to locate an applicable host with which to perform a resignature operation : Cluster ID = " +
|
||||||
|
volumeStoragePoolVO.getClusterId();
|
||||||
|
|
||||||
|
LOGGER.warn(noSupportForResignErrMsg);
|
||||||
|
|
||||||
|
throw new CloudRuntimeException(noSupportForResignErrMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
createVolumeFromSnapshot(snapshotInfo);
|
||||||
|
|
||||||
|
HostVO hostVO = getHost(snapshotInfo.getDataCenterId(), HypervisorType.XenServer, true);
|
||||||
|
|
||||||
|
copyCmdAnswer = performResignature(snapshotInfo, hostVO, null, true);
|
||||||
|
|
||||||
|
verifyCopyCmdAnswer(copyCmdAnswer, snapshotInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
|
||||||
|
int primaryStorageDownloadWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
|
||||||
|
|
||||||
|
CopyCommand copyCommand = new CopyCommand(snapshotInfo.getTO(), volumeInfo.getTO(), primaryStorageDownloadWait,
|
||||||
|
VirtualMachineManager.ExecuteInSequence.value());
|
||||||
|
|
||||||
|
HostVO hostVO = getHostInCluster(volumeStoragePoolVO.getClusterId());
|
||||||
|
|
||||||
|
if (!usingBackendSnapshot) {
|
||||||
|
long snapshotStoragePoolId = snapshotInfo.getDataStore().getId();
|
||||||
|
DataStore snapshotDataStore = dataStoreMgr.getDataStore(snapshotStoragePoolId, DataStoreRole.Primary);
|
||||||
|
|
||||||
|
_volumeService.grantAccess(snapshotInfo, hostVO, snapshotDataStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> srcDetails = getSnapshotDetails(snapshotInfo);
|
||||||
|
|
||||||
|
copyCommand.setOptions(srcDetails);
|
||||||
|
|
||||||
|
copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
|
||||||
|
|
||||||
|
if (!copyCmdAnswer.getResult()) {
|
||||||
|
errMsg = copyCmdAnswer.getDetails();
|
||||||
|
|
||||||
|
LOGGER.warn(errMsg);
|
||||||
|
|
||||||
|
throw new CloudRuntimeException(errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateNonManagedVolumeFromManagedSnapshot': " + ex.getMessage();
|
||||||
|
|
||||||
|
throw new CloudRuntimeException(errMsg);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
HostVO hostVO = getHostInCluster(volumeStoragePoolVO.getClusterId());
|
||||||
|
|
||||||
|
long snapshotStoragePoolId = snapshotInfo.getDataStore().getId();
|
||||||
|
DataStore snapshotDataStore = dataStoreMgr.getDataStore(snapshotStoragePoolId, DataStoreRole.Primary);
|
||||||
|
|
||||||
|
_volumeService.revokeAccess(snapshotInfo, hostVO, snapshotDataStore);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOGGER.debug("Failed to revoke access from dest volume", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usingBackendSnapshot) {
|
||||||
|
deleteVolumeFromSnapshot(snapshotInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (StringUtils.isEmpty(errMsg)) {
|
||||||
|
snapshotInfo.processEvent(Event.OperationSuccessed);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snapshotInfo.processEvent(Event.OperationFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LOGGER.warn("Error processing snapshot event: " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyCmdAnswer == null) {
|
||||||
|
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer);
|
||||||
|
|
||||||
|
result.setResult(errMsg);
|
||||||
|
|
||||||
|
callback.complete(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyCopyCmdAnswer(CopyCmdAnswer copyCmdAnswer, DataObject dataObject) {
|
||||||
|
if (copyCmdAnswer == null) {
|
||||||
|
throw new CloudRuntimeException("Unable to create a volume from a " + dataObject.getType().toString().toLowerCase() + " (copyCmdAnswer == null)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyCmdAnswer.getResult()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String details = copyCmdAnswer.getDetails();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(details)) {
|
||||||
|
throw new CloudRuntimeException("Unable to create a volume from a " + dataObject.getType().toString().toLowerCase() + " (no error details specified)");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CloudRuntimeException(details);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a volume on the storage from a snapshot that resides on the secondary storage (archived snapshot).
|
* Creates a managed volume on the storage from a snapshot that resides on the secondary storage (archived snapshot).
|
||||||
* @param snapshotInfo snapshot on secondary
|
* @param snapshotInfo snapshot on secondary
|
||||||
* @param volumeInfo volume to be created on the storage
|
* @param volumeInfo volume to be created on the storage
|
||||||
* @param callback for async
|
* @param callback for async
|
||||||
*/
|
*/
|
||||||
private void handleCreateVolumeFromSnapshotOnSecondaryStorage(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
|
private void handleCreateManagedVolumeFromNonManagedSnapshot(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
|
||||||
AsyncCompletionCallback<CopyCommandResult> callback) {
|
AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
String errMsg = null;
|
String errMsg = null;
|
||||||
CopyCmdAnswer copyCmdAnswer = null;
|
CopyCmdAnswer copyCmdAnswer = null;
|
||||||
|
|
||||||
@ -960,6 +1219,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
volumeInfo.processEvent(Event.MigrationRequested);
|
volumeInfo.processEvent(Event.MigrationRequested);
|
||||||
volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
|
volumeInfo = _volumeDataFactory.getVolume(volumeInfo.getId(), volumeInfo.getDataStore());
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
hostVO = getHost(snapshotInfo.getDataCenterId(), snapshotInfo.getHypervisorType(), false);
|
hostVO = getHost(snapshotInfo.getDataCenterId(), snapshotInfo.getHypervisorType(), false);
|
||||||
|
|
||||||
// copy the volume from secondary via the hypervisor
|
// copy the volume from secondary via the hypervisor
|
||||||
@ -980,12 +1241,13 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotOnSecondaryStorage': " +
|
errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateManagedVolumeFromNonManagedSnapshot': " + ex.getMessage();
|
||||||
ex.getMessage();
|
|
||||||
|
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
|
|
||||||
if (copyCmdAnswer == null) {
|
if (copyCmdAnswer == null) {
|
||||||
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
||||||
}
|
}
|
||||||
@ -1093,13 +1355,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
copyCmdAnswer = performResignature(volumeInfo, hostVO, extraDetails);
|
copyCmdAnswer = performResignature(volumeInfo, hostVO, extraDetails);
|
||||||
|
|
||||||
if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
|
verifyCopyCmdAnswer(copyCmdAnswer, templateInfo);
|
||||||
if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
|
|
||||||
throw new CloudRuntimeException(copyCmdAnswer.getDetails());
|
|
||||||
} else {
|
|
||||||
throw new CloudRuntimeException("Unable to create a volume from a template");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If using VMware, have the host rescan its software HBA if dynamic discovery is in use.
|
// If using VMware, have the host rescan its software HBA if dynamic discovery is in use.
|
||||||
if (HypervisorType.VMware.equals(templateInfo.getHypervisorType())) {
|
if (HypervisorType.VMware.equals(templateInfo.getHypervisorType())) {
|
||||||
@ -1145,11 +1401,13 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreateVolumeFromSnapshotBothOnStorageSystem(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
|
private void handleCreateManagedVolumeFromManagedSnapshot(SnapshotInfo snapshotInfo, VolumeInfo volumeInfo,
|
||||||
AsyncCompletionCallback<CopyCommandResult> callback) {
|
AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
String errMsg = null;
|
String errMsg = null;
|
||||||
CopyCmdAnswer copyCmdAnswer = null;
|
CopyCmdAnswer copyCmdAnswer = null;
|
||||||
|
|
||||||
|
boolean useCloning = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
verifyFormat(snapshotInfo);
|
verifyFormat(snapshotInfo);
|
||||||
|
|
||||||
@ -1171,8 +1429,9 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canStorageSystemCreateVolumeFromVolume = canStorageSystemCreateVolumeFromVolume(snapshotInfo);
|
boolean canStorageSystemCreateVolumeFromVolume = canStorageSystemCreateVolumeFromVolume(snapshotInfo.getDataStore().getId());
|
||||||
boolean useCloning = usingBackendSnapshot || (canStorageSystemCreateVolumeFromVolume && computeClusterSupportsVolumeClone);
|
|
||||||
|
useCloning = usingBackendSnapshot || (canStorageSystemCreateVolumeFromVolume && computeClusterSupportsVolumeClone);
|
||||||
|
|
||||||
VolumeDetailVO volumeDetail = null;
|
VolumeDetailVO volumeDetail = null;
|
||||||
|
|
||||||
@ -1232,16 +1491,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
// even when we don't need those hosts to do this kind of copy work
|
// even when we don't need those hosts to do this kind of copy work
|
||||||
hostVO = getHost(snapshotInfo.getDataCenterId(), snapshotInfo.getHypervisorType(), false);
|
hostVO = getHost(snapshotInfo.getDataCenterId(), snapshotInfo.getHypervisorType(), false);
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, hostVO);
|
copyCmdAnswer = performCopyOfVdi(volumeInfo, snapshotInfo, hostVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
|
verifyCopyCmdAnswer(copyCmdAnswer, snapshotInfo);
|
||||||
if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
|
|
||||||
throw new CloudRuntimeException(copyCmdAnswer.getDetails());
|
|
||||||
} else {
|
|
||||||
throw new CloudRuntimeException("Unable to create volume from snapshot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (HypervisorType.KVM.equals(snapshotInfo.getHypervisorType())) {
|
else if (HypervisorType.KVM.equals(snapshotInfo.getHypervisorType())) {
|
||||||
VolumeObjectTO newVolume = new VolumeObjectTO();
|
VolumeObjectTO newVolume = new VolumeObjectTO();
|
||||||
@ -1257,12 +1512,16 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateVolumeFromSnapshotBothOnStorageSystem': " +
|
errMsg = "Copy operation failed in 'StorageSystemDataMotionStrategy.handleCreateManagedVolumeFromManagedSnapshot': " +
|
||||||
ex.getMessage();
|
ex.getMessage();
|
||||||
|
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
if (useCloning) {
|
||||||
|
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
|
}
|
||||||
|
|
||||||
if (copyCmdAnswer == null) {
|
if (copyCmdAnswer == null) {
|
||||||
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
||||||
}
|
}
|
||||||
@ -1289,6 +1548,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
HostVO hostVO = getHost(dataCenterId, hypervisorType, false);
|
HostVO hostVO = getHost(dataCenterId, hypervisorType, false);
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
// copy the volume from secondary via the hypervisor
|
// copy the volume from secondary via the hypervisor
|
||||||
copyCmdAnswer = copyImageToVolume(srcVolumeInfo, destVolumeInfo, hostVO);
|
copyCmdAnswer = copyImageToVolume(srcVolumeInfo, destVolumeInfo, hostVO);
|
||||||
|
|
||||||
@ -1308,6 +1569,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
|
|
||||||
if (copyCmdAnswer == null) {
|
if (copyCmdAnswer == null) {
|
||||||
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
copyCmdAnswer = new CopyCmdAnswer(errMsg);
|
||||||
}
|
}
|
||||||
@ -1393,6 +1656,15 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState qualityOfServiceState) {
|
||||||
|
try {
|
||||||
|
((PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver()).handleQualityOfServiceForVolumeMigration(volumeInfo, qualityOfServiceState);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LOGGER.warn(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private SnapshotDetailsVO handleSnapshotDetails(long csSnapshotId, String value) {
|
private SnapshotDetailsVO handleSnapshotDetails(long csSnapshotId, String value) {
|
||||||
String name = "tempVolume";
|
String name = "tempVolume";
|
||||||
|
|
||||||
@ -1452,6 +1724,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
|
destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
_volumeService.grantAccess(destVolumeInfo, destHost, destDataStore);
|
_volumeService.grantAccess(destVolumeInfo, destHost, destDataStore);
|
||||||
|
|
||||||
String connectedPath = connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
|
String connectedPath = connectHostToVolume(destHost, destVolumeInfo.getPoolId(), destVolumeInfo.get_iScsiName());
|
||||||
@ -1554,6 +1828,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
VolumeInfo srcVolumeInfo = entry.getKey();
|
VolumeInfo srcVolumeInfo = entry.getKey();
|
||||||
VolumeInfo destVolumeInfo = entry.getValue();
|
VolumeInfo destVolumeInfo = entry.getValue();
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
srcVolumeInfo.processEvent(Event.OperationSuccessed);
|
srcVolumeInfo.processEvent(Event.OperationSuccessed);
|
||||||
destVolumeInfo.processEvent(Event.OperationSuccessed);
|
destVolumeInfo.processEvent(Event.OperationSuccessed);
|
||||||
@ -1721,20 +1997,28 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canStorageSystemCreateVolumeFromVolume(SnapshotInfo snapshotInfo) {
|
private boolean canStorageSystemCreateVolumeFromVolume(long storagePoolId) {
|
||||||
boolean supportsCloningVolumeFromVolume = false;
|
return storageSystemSupportsCapability(storagePoolId, DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString());
|
||||||
|
}
|
||||||
|
|
||||||
DataStore dataStore = dataStoreMgr.getDataStore(snapshotInfo.getDataStore().getId(), DataStoreRole.Primary);
|
private boolean canStorageSystemCreateVolumeFromSnapshot(long storagePoolId) {
|
||||||
|
return storageSystemSupportsCapability(storagePoolId, DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean storageSystemSupportsCapability(long storagePoolId, String capability) {
|
||||||
|
boolean supportsCapability = false;
|
||||||
|
|
||||||
|
DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
|
||||||
|
|
||||||
Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();
|
Map<String, String> mapCapabilities = dataStore.getDriver().getCapabilities();
|
||||||
|
|
||||||
if (mapCapabilities != null) {
|
if (mapCapabilities != null) {
|
||||||
String value = mapCapabilities.get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString());
|
String value = mapCapabilities.get(capability);
|
||||||
|
|
||||||
supportsCloningVolumeFromVolume = Boolean.valueOf(value);
|
supportsCapability = Boolean.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return supportsCloningVolumeFromVolume;
|
return supportsCapability;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getVolumeProperty(long volumeId, String property) {
|
private String getVolumeProperty(long volumeId, String property) {
|
||||||
@ -1757,7 +2041,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreateTemplateFromVolume(VolumeInfo volumeInfo, TemplateInfo templateInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
|
private void handleCreateTemplateFromManagedVolume(VolumeInfo volumeInfo, TemplateInfo templateInfo, AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||||
boolean srcVolumeDetached = volumeInfo.getAttachedVM() == null;
|
boolean srcVolumeDetached = volumeInfo.getAttachedVM() == null;
|
||||||
|
|
||||||
String errMsg = null;
|
String errMsg = null;
|
||||||
@ -1775,9 +2059,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
|
String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString());
|
||||||
int primaryStorageDownloadWait = NumberUtils.toInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
|
int primaryStorageDownloadWait = NumberUtils.toInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue()));
|
||||||
|
|
||||||
CopyCommand copyCommand = new CopyCommand(volumeInfo.getTO(), templateInfo.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
|
CopyCommand copyCommand = new CopyCommand(volumeInfo.getTO(), templateInfo.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
if (srcVolumeDetached) {
|
if (srcVolumeDetached) {
|
||||||
_volumeService.grantAccess(volumeInfo, hostVO, srcDataStore);
|
_volumeService.grantAccess(volumeInfo, hostVO, srcDataStore);
|
||||||
}
|
}
|
||||||
@ -1789,8 +2076,10 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
|
copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand);
|
||||||
|
|
||||||
if (!copyCmdAnswer.getResult()) {
|
if (!copyCmdAnswer.getResult()) {
|
||||||
// We were not able to copy. Handle it.
|
|
||||||
errMsg = copyCmdAnswer.getDetails();
|
errMsg = copyCmdAnswer.getDetails();
|
||||||
|
|
||||||
|
LOGGER.warn(errMsg);
|
||||||
|
|
||||||
throw new CloudRuntimeException(errMsg);
|
throw new CloudRuntimeException(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1808,14 +2097,17 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
throw new CloudRuntimeException(msg + ex.getMessage(), ex);
|
throw new CloudRuntimeException(msg + ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
try {
|
if (srcVolumeDetached) {
|
||||||
if (srcVolumeDetached) {
|
try {
|
||||||
_volumeService.revokeAccess(volumeInfo, hostVO, srcDataStore);
|
_volumeService.revokeAccess(volumeInfo, hostVO, srcDataStore);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LOGGER.warn("Error revoking access to volume (Volume ID = " + volumeInfo.getId() + "): " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
|
||||||
LOGGER.warn("Error revoking access to volume (Volume ID = " + volumeInfo.getId() + "): " + ex.getMessage(), ex);
|
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
}
|
|
||||||
if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
|
if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) {
|
||||||
if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
|
if (copyCmdAnswer != null && !StringUtils.isEmpty(copyCmdAnswer.getDetails())) {
|
||||||
errMsg = copyCmdAnswer.getDetails();
|
errMsg = copyCmdAnswer.getDetails();
|
||||||
@ -2104,7 +2396,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
return leafData;
|
return leafData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String migrateVolume(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
|
private String migrateVolumeForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
|
||||||
boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
|
boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2118,6 +2410,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
_volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
|
_volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
_volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
|
_volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore());
|
||||||
|
|
||||||
MigrateVolumeAnswer migrateVolumeAnswer = (MigrateVolumeAnswer)_agentMgr.send(hostVO.getId(), migrateVolumeCommand);
|
MigrateVolumeAnswer migrateVolumeAnswer = (MigrateVolumeAnswer)_agentMgr.send(hostVO.getId(), migrateVolumeCommand);
|
||||||
@ -2164,9 +2458,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
|
|
||||||
throw new CloudRuntimeException(msg + ex.getMessage(), ex);
|
throw new CloudRuntimeException(msg + ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String copyVolumeToSecondaryStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
|
private String copyManagedVolumeToSecondaryStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) {
|
||||||
boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
|
boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -2179,6 +2476,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
copyVolumeCommand.setSrcData(srcVolumeInfo.getTO());
|
copyVolumeCommand.setSrcData(srcVolumeInfo.getTO());
|
||||||
copyVolumeCommand.setSrcDetails(srcDetails);
|
copyVolumeCommand.setSrcDetails(srcDetails);
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(srcVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);
|
||||||
|
|
||||||
if (srcVolumeDetached) {
|
if (srcVolumeDetached) {
|
||||||
_volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
|
_volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
|
||||||
}
|
}
|
||||||
@ -2207,6 +2506,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||||||
if (srcVolumeDetached) {
|
if (srcVolumeDetached) {
|
||||||
_volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
|
_volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleQualityOfServiceForVolumeMigration(srcVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1517,67 +1517,114 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isManaged(Map<String, String> options) {
|
||||||
|
if (options == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String iqn = options.get(DiskTO.IQN);
|
||||||
|
|
||||||
|
if (iqn == null || iqn.trim().length() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String storageHost = options.get(DiskTO.STORAGE_HOST);
|
||||||
|
|
||||||
|
if (storageHost == null || storageHost.trim().length() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCreateManagedVolumeFromManagedSnapshot(Map<String, String> volumeOptions, Map<String, String> snapshotOptions) {
|
||||||
|
return isManaged(volumeOptions) && isManaged(snapshotOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCreateNonManagedVolumeFromManagedSnapshot(Map<String, String> volumeOptions, Map<String, String> snapshotOptions) {
|
||||||
|
return !isManaged(volumeOptions) && isManaged(snapshotOptions);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Answer createVolumeFromSnapshot(final CopyCommand cmd) {
|
public Answer createVolumeFromSnapshot(final CopyCommand cmd) {
|
||||||
final Connection conn = hypervisorResource.getConnection();
|
Connection conn = hypervisorResource.getConnection();
|
||||||
final DataTO srcData = cmd.getSrcTO();
|
|
||||||
final SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData;
|
|
||||||
final DataTO destData = cmd.getDestTO();
|
|
||||||
final DataStoreTO imageStore = srcData.getDataStore();
|
|
||||||
|
|
||||||
if (srcData.getDataStore() instanceof PrimaryDataStoreTO && destData.getDataStore() instanceof PrimaryDataStoreTO) {
|
DataTO srcData = cmd.getSrcTO();
|
||||||
return createVolumeFromSnapshot2(cmd);
|
SnapshotObjectTO snapshot = (SnapshotObjectTO)srcData;
|
||||||
|
DataStoreTO imageStore = srcData.getDataStore();
|
||||||
|
DataTO destData = cmd.getDestTO();
|
||||||
|
|
||||||
|
if (isCreateManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
|
||||||
|
return createManagedVolumeFromManagedSnapshot(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCreateNonManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
|
||||||
|
return createNonManagedVolumeFromManagedSnapshot(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(imageStore instanceof NfsTO)) {
|
if (!(imageStore instanceof NfsTO)) {
|
||||||
return new CopyCmdAnswer("unsupported protocol");
|
return new CopyCmdAnswer("unsupported protocol");
|
||||||
}
|
}
|
||||||
|
|
||||||
final NfsTO nfsImageStore = (NfsTO) imageStore;
|
NfsTO nfsImageStore = (NfsTO)imageStore;
|
||||||
final String primaryStorageNameLabel = destData.getDataStore().getUuid();
|
String primaryStorageNameLabel = destData.getDataStore().getUuid();
|
||||||
final String secondaryStorageUrl = nfsImageStore.getUrl();
|
String secondaryStorageUrl = nfsImageStore.getUrl();
|
||||||
final int wait = cmd.getWait();
|
|
||||||
|
int wait = cmd.getWait();
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
|
|
||||||
// Generic error message.
|
// Generic error message.
|
||||||
String details = null;
|
String details;
|
||||||
String volumeUUID = null;
|
String volumeUUID;
|
||||||
|
|
||||||
if (secondaryStorageUrl == null) {
|
if (secondaryStorageUrl == null) {
|
||||||
details += " because the URL passed: " + secondaryStorageUrl + " is invalid.";
|
details = "The URL passed in 'null'.";
|
||||||
|
|
||||||
return new CopyCmdAnswer(details);
|
return new CopyCmdAnswer(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final SR primaryStorageSR = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
|
SR primaryStorageSR = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
|
||||||
|
|
||||||
if (primaryStorageSR == null) {
|
if (primaryStorageSR == null) {
|
||||||
throw new InternalErrorException("Could not create volume from snapshot because the primary Storage SR could not be created from the name label: " +
|
throw new InternalErrorException("Could not create volume from snapshot because the primary storage SR could not be " +
|
||||||
primaryStorageNameLabel);
|
"created from the name label: " + primaryStorageNameLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the absolute path of the snapshot on the secondary storage.
|
// Get the absolute path of the snapshot on the secondary storage.
|
||||||
String snapshotInstallPath = snapshot.getPath();
|
String snapshotInstallPath = snapshot.getPath();
|
||||||
final int index = snapshotInstallPath.lastIndexOf(nfsImageStore.getPathSeparator());
|
int index = snapshotInstallPath.lastIndexOf(nfsImageStore.getPathSeparator());
|
||||||
final String snapshotName = snapshotInstallPath.substring(index + 1);
|
String snapshotName = snapshotInstallPath.substring(index + 1);
|
||||||
|
|
||||||
if (!snapshotName.startsWith("VHD-") && !snapshotName.endsWith(".vhd")) {
|
if (!snapshotName.startsWith("VHD-") && !snapshotName.endsWith(".vhd")) {
|
||||||
snapshotInstallPath = snapshotInstallPath + ".vhd";
|
snapshotInstallPath = snapshotInstallPath + ".vhd";
|
||||||
}
|
}
|
||||||
final URI snapshotURI = new URI(secondaryStorageUrl + nfsImageStore.getPathSeparator() + snapshotInstallPath);
|
|
||||||
final String snapshotPath = snapshotURI.getHost() + ":" + snapshotURI.getPath();
|
URI snapshotURI = new URI(secondaryStorageUrl + nfsImageStore.getPathSeparator() + snapshotInstallPath);
|
||||||
final String srUuid = primaryStorageSR.getUuid(conn);
|
String snapshotPath = snapshotURI.getHost() + ":" + snapshotURI.getPath();
|
||||||
|
String srUuid = primaryStorageSR.getUuid(conn);
|
||||||
|
|
||||||
volumeUUID = copy_vhd_from_secondarystorage(conn, snapshotPath, srUuid, wait);
|
volumeUUID = copy_vhd_from_secondarystorage(conn, snapshotPath, srUuid, wait);
|
||||||
result = true;
|
result = true;
|
||||||
final VDI volume = VDI.getByUuid(conn, volumeUUID);
|
|
||||||
final VDI.Record vdir = volume.getRecord(conn);
|
VDI volume = VDI.getByUuid(conn, volumeUUID);
|
||||||
final VolumeObjectTO newVol = new VolumeObjectTO();
|
VDI.Record vdir = volume.getRecord(conn);
|
||||||
|
VolumeObjectTO newVol = new VolumeObjectTO();
|
||||||
|
|
||||||
newVol.setPath(volumeUUID);
|
newVol.setPath(volumeUUID);
|
||||||
newVol.setSize(vdir.virtualSize);
|
newVol.setSize(vdir.virtualSize);
|
||||||
|
|
||||||
return new CopyCmdAnswer(newVol);
|
return new CopyCmdAnswer(newVol);
|
||||||
} catch (final XenAPIException e) {
|
} catch (final XenAPIException e) {
|
||||||
details += " due to " + e.toString();
|
details = "Exception due to " + e.toString();
|
||||||
|
|
||||||
s_logger.warn(details, e);
|
s_logger.warn(details, e);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
details += " due to " + e.getMessage();
|
details = "Exception due to " + e.getMessage();
|
||||||
|
|
||||||
s_logger.warn(details, e);
|
s_logger.warn(details, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
// Is this logged at a higher level?
|
// Is this logged at a higher level?
|
||||||
s_logger.error(details);
|
s_logger.error(details);
|
||||||
@ -1587,7 +1634,7 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
|||||||
return new CopyCmdAnswer(details);
|
return new CopyCmdAnswer(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Answer createVolumeFromSnapshot2(final CopyCommand cmd) {
|
Answer createManagedVolumeFromManagedSnapshot(final CopyCommand cmd) {
|
||||||
try {
|
try {
|
||||||
final Connection conn = hypervisorResource.getConnection();
|
final Connection conn = hypervisorResource.getConnection();
|
||||||
|
|
||||||
@ -1632,6 +1679,51 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Answer createNonManagedVolumeFromManagedSnapshot(final CopyCommand cmd) {
|
||||||
|
Connection conn = hypervisorResource.getConnection();
|
||||||
|
SR srcSr = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> srcOptions = cmd.getOptions();
|
||||||
|
|
||||||
|
String src_iScsiName = srcOptions.get(DiskTO.IQN);
|
||||||
|
String srcStorageHost = srcOptions.get(DiskTO.STORAGE_HOST);
|
||||||
|
String srcChapInitiatorUsername = srcOptions.get(DiskTO.CHAP_INITIATOR_USERNAME);
|
||||||
|
String srcChapInitiatorSecret = srcOptions.get(DiskTO.CHAP_INITIATOR_SECRET);
|
||||||
|
|
||||||
|
srcSr = hypervisorResource.getIscsiSR(conn, src_iScsiName, srcStorageHost, src_iScsiName,
|
||||||
|
srcChapInitiatorUsername, srcChapInitiatorSecret, false);
|
||||||
|
|
||||||
|
// there should only be one VDI in this SR
|
||||||
|
VDI srcVdi = srcSr.getVDIs(conn).iterator().next();
|
||||||
|
|
||||||
|
DataTO destData = cmd.getDestTO();
|
||||||
|
String primaryStorageNameLabel = destData.getDataStore().getUuid();
|
||||||
|
|
||||||
|
SR destSr = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
|
||||||
|
|
||||||
|
VDI vdiCopy = srcVdi.copy(conn, destSr);
|
||||||
|
|
||||||
|
VolumeObjectTO newVol = new VolumeObjectTO();
|
||||||
|
|
||||||
|
newVol.setSize(vdiCopy.getVirtualSize(conn));
|
||||||
|
newVol.setPath(vdiCopy.getUuid(conn));
|
||||||
|
newVol.setFormat(ImageFormat.VHD);
|
||||||
|
|
||||||
|
return new CopyCmdAnswer(newVol);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
s_logger.warn("Failed to copy snapshot to volume: " + ex.toString(), ex);
|
||||||
|
|
||||||
|
return new CopyCmdAnswer(ex.getMessage());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (srcSr != null) {
|
||||||
|
hypervisorResource.removeSR(conn, srcSr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Answer deleteSnapshot(final DeleteCommand cmd) {
|
public Answer deleteSnapshot(final DeleteCommand cmd) {
|
||||||
final SnapshotObjectTO snapshot = (SnapshotObjectTO) cmd.getData();
|
final SnapshotObjectTO snapshot = (SnapshotObjectTO) cmd.getData();
|
||||||
|
|||||||
@ -787,8 +787,12 @@ public class Xenserver625StorageProcessor extends XenServerStorageProcessor {
|
|||||||
final VolumeObjectTO volume = (VolumeObjectTO)destData;
|
final VolumeObjectTO volume = (VolumeObjectTO)destData;
|
||||||
final DataStoreTO imageStore = srcData.getDataStore();
|
final DataStoreTO imageStore = srcData.getDataStore();
|
||||||
|
|
||||||
if (srcData.getDataStore() instanceof PrimaryDataStoreTO && destData.getDataStore() instanceof PrimaryDataStoreTO) {
|
if (isCreateManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
|
||||||
return createVolumeFromSnapshot2(cmd);
|
return createManagedVolumeFromManagedSnapshot(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCreateNonManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
|
||||||
|
return createNonManagedVolumeFromManagedSnapshot(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(imageStore instanceof NfsTO)) {
|
if (!(imageStore instanceof NfsTO)) {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import org.apache.log4j.Logger;
|
|||||||
import com.cloud.agent.api.Answer;
|
import com.cloud.agent.api.Answer;
|
||||||
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
|
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
|
||||||
import com.cloud.agent.api.storage.MigrateVolumeCommand;
|
import com.cloud.agent.api.storage.MigrateVolumeCommand;
|
||||||
|
import com.cloud.agent.api.to.DiskTO;
|
||||||
import com.cloud.agent.api.to.StorageFilerTO;
|
import com.cloud.agent.api.to.StorageFilerTO;
|
||||||
import com.cloud.hypervisor.xenserver.resource.XenServer610Resource;
|
import com.cloud.hypervisor.xenserver.resource.XenServer610Resource;
|
||||||
import com.cloud.resource.CommandWrapper;
|
import com.cloud.resource.CommandWrapper;
|
||||||
@ -39,35 +40,59 @@ import com.xensource.xenapi.VDI;
|
|||||||
|
|
||||||
@ResourceWrapper(handles = MigrateVolumeCommand.class)
|
@ResourceWrapper(handles = MigrateVolumeCommand.class)
|
||||||
public final class XenServer610MigrateVolumeCommandWrapper extends CommandWrapper<MigrateVolumeCommand, Answer, XenServer610Resource> {
|
public final class XenServer610MigrateVolumeCommandWrapper extends CommandWrapper<MigrateVolumeCommand, Answer, XenServer610Resource> {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(XenServer610MigrateVolumeCommandWrapper.class);
|
||||||
private static final Logger s_logger = Logger.getLogger(XenServer610MigrateVolumeCommandWrapper.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Answer execute(final MigrateVolumeCommand command, final XenServer610Resource xenServer610Resource) {
|
public Answer execute(final MigrateVolumeCommand command, final XenServer610Resource xenServer610Resource) {
|
||||||
final Connection connection = xenServer610Resource.getConnection();
|
Connection connection = xenServer610Resource.getConnection();
|
||||||
final String volumeUUID = command.getVolumePath();
|
String srcVolumeUuid = command.getVolumePath();
|
||||||
final StorageFilerTO poolTO = command.getPool();
|
SR destPool = null;
|
||||||
|
Map<String, String> destDetails = command.getDestDetails();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String uuid = poolTO.getUuid();
|
VDI srcVolume = xenServer610Resource.getVDIbyUuid(connection, srcVolumeUuid);
|
||||||
final SR destinationPool = xenServer610Resource.getStorageRepository(connection, uuid);
|
|
||||||
final VDI srcVolume = xenServer610Resource.getVDIbyUuid(connection, volumeUUID);
|
if (destDetails != null && Boolean.parseBoolean(destDetails.get(DiskTO.MANAGED))) {
|
||||||
final Map<String, String> other = new HashMap<String, String>();
|
String iScsiName = destDetails.get(DiskTO.IQN);
|
||||||
|
String storageHost = destDetails.get(DiskTO.STORAGE_HOST);
|
||||||
|
String chapInitiatorUsername = destDetails.get(DiskTO.CHAP_INITIATOR_USERNAME);
|
||||||
|
String chapInitiatorSecret = destDetails.get(DiskTO.CHAP_INITIATOR_SECRET);
|
||||||
|
|
||||||
|
destPool = xenServer610Resource.getIscsiSR(connection, iScsiName, storageHost, iScsiName,
|
||||||
|
chapInitiatorUsername, chapInitiatorSecret, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
StorageFilerTO destPoolTO = command.getPool();
|
||||||
|
String destPoolUuid = destPoolTO.getUuid();
|
||||||
|
|
||||||
|
destPool = xenServer610Resource.getStorageRepository(connection, destPoolUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> other = new HashMap<>();
|
||||||
|
|
||||||
other.put("live", "true");
|
other.put("live", "true");
|
||||||
|
|
||||||
// Live migrate the vdi across pool.
|
// Live migrate the VDI.
|
||||||
final Task task = srcVolume.poolMigrateAsync(connection, destinationPool, other);
|
Task task = srcVolume.poolMigrateAsync(connection, destPool, other);
|
||||||
final long timeout = xenServer610Resource.getMigrateWait() * 1000L;
|
|
||||||
|
long timeout = xenServer610Resource.getMigrateWait() * 1000L;
|
||||||
|
|
||||||
xenServer610Resource.waitForTask(connection, task, 1000, timeout);
|
xenServer610Resource.waitForTask(connection, task, 1000, timeout);
|
||||||
xenServer610Resource.checkForSuccess(connection, task);
|
xenServer610Resource.checkForSuccess(connection, task);
|
||||||
|
|
||||||
final VDI dvdi = Types.toVDI(task, connection);
|
VDI destVdi = Types.toVDI(task, connection);
|
||||||
|
|
||||||
|
return new MigrateVolumeAnswer(command, true, null, destVdi.getUuid(connection));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (destDetails != null && Boolean.parseBoolean(destDetails.get(DiskTO.MANAGED)) && destPool != null) {
|
||||||
|
xenServer610Resource.removeSR(connection, destPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
String msg = "Caught exception " + ex.getClass().getName() + " due to the following: " + ex.toString();
|
||||||
|
|
||||||
|
LOGGER.error(msg, ex);
|
||||||
|
|
||||||
return new MigrateVolumeAnswer(command, true, null, dvdi.getUuid(connection));
|
|
||||||
} catch (final Exception e) {
|
|
||||||
final String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.toString();
|
|
||||||
s_logger.error(msg, e);
|
|
||||||
return new MigrateVolumeAnswer(command, false, msg, null);
|
return new MigrateVolumeAnswer(command, false, msg, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -321,6 +321,9 @@ public class ElastistorPrimaryDataStoreDriver extends CloudStackPrimaryDataStore
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
|
||||||
|
|
||||||
//this method will utilize the volume details table to add third party volume properties
|
//this method will utilize the volume details table to add third party volume properties
|
||||||
public void updateVolumeDetails(VolumeVO volume, FileSystem esvolume) {
|
public void updateVolumeDetails(VolumeVO volume, FileSystem esvolume) {
|
||||||
|
|
||||||
|
|||||||
@ -385,4 +385,7 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri
|
|||||||
|
|
||||||
callback.complete(result);
|
callback.complete(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,4 +206,7 @@ public class NexentaPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
|
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
|||||||
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
|
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.framework.async.AsyncCallbackDispatcher;
|
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
|
||||||
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
|
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
|
||||||
import org.apache.cloudstack.framework.async.AsyncRpcContext;
|
import org.apache.cloudstack.framework.async.AsyncRpcContext;
|
||||||
@ -227,6 +228,10 @@ public class SamplePrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
|
|||||||
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
|
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
|
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
private static final long MAX_IOPS_FOR_TEMPLATE_VOLUME = 20000L;
|
private static final long MAX_IOPS_FOR_TEMPLATE_VOLUME = 20000L;
|
||||||
private static final long MIN_IOPS_FOR_TEMP_VOLUME = 100L;
|
private static final long MIN_IOPS_FOR_TEMP_VOLUME = 100L;
|
||||||
private static final long MAX_IOPS_FOR_TEMP_VOLUME = 20000L;
|
private static final long MAX_IOPS_FOR_TEMP_VOLUME = 20000L;
|
||||||
|
private static final long MAX_IOPS_FOR_MIGRATING_VOLUME = 20000L;
|
||||||
private static final long MIN_IOPS_FOR_SNAPSHOT_VOLUME = 100L;
|
private static final long MIN_IOPS_FOR_SNAPSHOT_VOLUME = 100L;
|
||||||
private static final long MAX_IOPS_FOR_SNAPSHOT_VOLUME = 20000L;
|
private static final long MAX_IOPS_FOR_SNAPSHOT_VOLUME = 20000L;
|
||||||
|
|
||||||
@ -686,6 +687,10 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE_FAILURE);
|
return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBasicDeleteByFolder(long volumeId) {
|
||||||
|
return getBooleanValueFromVolumeDetails(volumeId, PrimaryDataStoreDriver.BASIC_DELETE_BY_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isBasicGrantAccess(long volumeId) {
|
private boolean isBasicGrantAccess(long volumeId) {
|
||||||
return getBooleanValueFromVolumeDetails(volumeId, BASIC_GRANT_ACCESS);
|
return getBooleanValueFromVolumeDetails(volumeId, BASIC_GRANT_ACCESS);
|
||||||
}
|
}
|
||||||
@ -1218,13 +1223,30 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE_FAILURE);
|
volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void performBasicDeleteByFolder(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) {
|
||||||
|
VolumeVO volumeVO = volumeDao.findById(volumeId);
|
||||||
|
|
||||||
|
Preconditions.checkNotNull(volumeVO, "'volumeVO' should not be 'null'.");
|
||||||
|
|
||||||
|
String folder = volumeVO.getFolder();
|
||||||
|
|
||||||
|
Preconditions.checkNotNull(folder, "'folder' should not be 'null'.");
|
||||||
|
|
||||||
|
long sfVolumeId = Long.parseLong(folder);
|
||||||
|
|
||||||
|
SolidFireUtil.deleteVolume(sfConnection, sfVolumeId);
|
||||||
|
}
|
||||||
|
|
||||||
private void deleteVolume(VolumeInfo volumeInfo, long storagePoolId) {
|
private void deleteVolume(VolumeInfo volumeInfo, long storagePoolId) {
|
||||||
try {
|
try {
|
||||||
long volumeId = volumeInfo.getId();
|
long volumeId = volumeInfo.getId();
|
||||||
|
|
||||||
SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao);
|
SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao);
|
||||||
|
|
||||||
if (isBasicDelete(volumeId)) {
|
if (isBasicDeleteByFolder(volumeId)) {
|
||||||
|
performBasicDeleteByFolder(sfConnection, volumeId);
|
||||||
|
}
|
||||||
|
else if (isBasicDelete(volumeId)) {
|
||||||
performBasicDelete(sfConnection, volumeId);
|
performBasicDelete(sfConnection, volumeId);
|
||||||
}
|
}
|
||||||
else if (isBasicDeleteFailure(volumeId)) {
|
else if (isBasicDeleteFailure(volumeId)) {
|
||||||
@ -1436,6 +1458,21 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {
|
||||||
|
SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(volumeInfo.getPoolId(), storagePoolDetailsDao);
|
||||||
|
|
||||||
|
Iops iops;
|
||||||
|
|
||||||
|
if (QualityOfServiceState.MIGRATION.equals(qualityOfServiceState)) {
|
||||||
|
iops = getIops(volumeInfo.getMinIops(), MAX_IOPS_FOR_MIGRATING_VOLUME, volumeInfo.getPoolId());
|
||||||
|
} else {
|
||||||
|
iops = getIops(volumeInfo.getMinIops(), volumeInfo.getMaxIops(), volumeInfo.getPoolId());
|
||||||
|
}
|
||||||
|
|
||||||
|
SolidFireUtil.modifyVolumeQoS(sfConnection, Long.parseLong(volumeInfo.getFolder()), iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops());
|
||||||
|
}
|
||||||
|
|
||||||
private void verifySufficientBytesForStoragePool(long requestedBytes, long storagePoolId) {
|
private void verifySufficientBytesForStoragePool(long requestedBytes, long storagePoolId) {
|
||||||
StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
|
StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId);
|
||||||
|
|
||||||
|
|||||||
@ -857,6 +857,15 @@ public class SolidFireUtil {
|
|||||||
getSolidFireElement(sfConnection).modifyVolume(request);
|
getSolidFireElement(sfConnection).modifyVolume(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void modifyVolumeQoS(SolidFireConnection sfConnection, long volumeId, long minIops, long maxIops, long burstIops) {
|
||||||
|
ModifyVolumeRequest request = ModifyVolumeRequest.builder()
|
||||||
|
.volumeID(volumeId)
|
||||||
|
.optionalQos(new QoS(Optional.of(minIops), Optional.of(maxIops), Optional.of(burstIops), Optional.EMPTY_LONG))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
getSolidFireElement(sfConnection).modifyVolume(request);
|
||||||
|
}
|
||||||
|
|
||||||
public static SolidFireVolume getVolume(SolidFireConnection sfConnection, long volumeId) {
|
public static SolidFireVolume getVolume(SolidFireConnection sfConnection, long volumeId) {
|
||||||
ListVolumesRequest request = ListVolumesRequest.builder()
|
ListVolumesRequest request = ListVolumesRequest.builder()
|
||||||
.optionalStartVolumeID(volumeId)
|
.optionalStartVolumeID(volumeId)
|
||||||
|
|||||||
402
test/integration/plugins/solidfire/TestOnlineStorageMigration.py
Normal file
402
test/integration/plugins/solidfire/TestOnlineStorageMigration.py
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import SignedAPICall
|
||||||
|
import XenAPI
|
||||||
|
|
||||||
|
from solidfire.factory import ElementFactory
|
||||||
|
|
||||||
|
from util import sf_util
|
||||||
|
|
||||||
|
# All tests inherit from cloudstackTestCase
|
||||||
|
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||||
|
|
||||||
|
# Import Integration Libraries
|
||||||
|
|
||||||
|
# base - contains all resources as entities and defines create, delete, list operations on them
|
||||||
|
from marvin.lib.base import Account, ServiceOffering, Snapshot, StoragePool, User, VirtualMachine, Volume
|
||||||
|
|
||||||
|
# common - commonly used methods for all tests are listed here
|
||||||
|
from marvin.lib.common import list_clusters, get_domain, list_hosts, list_snapshots, get_template, list_volumes, get_zone
|
||||||
|
|
||||||
|
# utils - utility classes for common cleanup, external library wrappers, etc.
|
||||||
|
from marvin.lib.utils import cleanup_resources
|
||||||
|
|
||||||
|
# Prerequisites:
|
||||||
|
# Only one zone
|
||||||
|
# Only one pod
|
||||||
|
# Only one cluster
|
||||||
|
#
|
||||||
|
# Running the tests:
|
||||||
|
# If using XenServer, verify the "xen_server_hostname" variable is correct.
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
# If you do have more than one cluster, you might need to change this line: cls.cluster = list_clusters(cls.apiClient)[0]
|
||||||
|
|
||||||
|
|
||||||
|
class TestData():
|
||||||
|
# constants
|
||||||
|
account = "account"
|
||||||
|
capacityBytes = "capacitybytes"
|
||||||
|
capacityIops = "capacityiops"
|
||||||
|
clusterId = "clusterId"
|
||||||
|
computeOffering = "computeoffering"
|
||||||
|
domainId = "domainId"
|
||||||
|
email = "email"
|
||||||
|
firstname = "firstname"
|
||||||
|
hypervisor = "hypervisor"
|
||||||
|
lastname = "lastname"
|
||||||
|
managementServer = "managementServer"
|
||||||
|
mvip = "mvip"
|
||||||
|
name = "name"
|
||||||
|
nfs_folder = "/export/primary2"
|
||||||
|
nfs_path = "nfs://10.117.40.114" + nfs_folder
|
||||||
|
nfs_storage_tag = "NFS-123"
|
||||||
|
password = "password"
|
||||||
|
podId = "podId"
|
||||||
|
port = "port"
|
||||||
|
primaryStorage = "primarystorage"
|
||||||
|
primaryStorage2 = "primarystorage2"
|
||||||
|
provider = "provider"
|
||||||
|
scope = "scope"
|
||||||
|
solidFire = "solidfire"
|
||||||
|
storageTag = "SolidFire_SAN_1"
|
||||||
|
tags = "tags"
|
||||||
|
url = "url"
|
||||||
|
user = "user"
|
||||||
|
username = "username"
|
||||||
|
xenServer = "xenserver"
|
||||||
|
zoneId = "zoneId"
|
||||||
|
|
||||||
|
hypervisor_type = xenServer
|
||||||
|
xen_server_hostname = "XenServer-6.5-1"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.testdata = {
|
||||||
|
TestData.solidFire: {
|
||||||
|
TestData.mvip: "10.117.40.120",
|
||||||
|
TestData.username: "admin",
|
||||||
|
TestData.password: "admin",
|
||||||
|
TestData.port: 443,
|
||||||
|
TestData.url: "https://10.117.40.120:443"
|
||||||
|
},
|
||||||
|
TestData.xenServer: {
|
||||||
|
TestData.username: "root",
|
||||||
|
TestData.password: "solidfire"
|
||||||
|
},
|
||||||
|
TestData.managementServer: {
|
||||||
|
TestData.username: "cloudstack",
|
||||||
|
TestData.password: "solidfire"
|
||||||
|
},
|
||||||
|
TestData.account: {
|
||||||
|
TestData.email: "test@test.com",
|
||||||
|
TestData.firstname: "John",
|
||||||
|
TestData.lastname: "Doe",
|
||||||
|
TestData.username: "test",
|
||||||
|
TestData.password: "test"
|
||||||
|
},
|
||||||
|
TestData.user: {
|
||||||
|
TestData.email: "user@test.com",
|
||||||
|
TestData.firstname: "Jane",
|
||||||
|
TestData.lastname: "Doe",
|
||||||
|
TestData.username: "testuser",
|
||||||
|
TestData.password: "password"
|
||||||
|
},
|
||||||
|
TestData.primaryStorage: {
|
||||||
|
TestData.name: "NFS-%d" % random.randint(0, 100),
|
||||||
|
TestData.scope: "CLUSTER",
|
||||||
|
TestData.clusterId: 1,
|
||||||
|
TestData.url: TestData.nfs_path,
|
||||||
|
TestData.tags: TestData.nfs_storage_tag
|
||||||
|
},
|
||||||
|
TestData.primaryStorage2: {
|
||||||
|
TestData.name: "SolidFire-%d" % random.randint(0, 100),
|
||||||
|
TestData.scope: "ZONE",
|
||||||
|
TestData.url: "MVIP=10.117.40.120;SVIP=10.117.41.120;" +
|
||||||
|
"clusterAdminUsername=admin;clusterAdminPassword=admin;" +
|
||||||
|
"clusterDefaultMinIops=10000;clusterDefaultMaxIops=15000;" +
|
||||||
|
"clusterDefaultBurstIopsPercentOfMaxIops=1.5;",
|
||||||
|
TestData.provider: "SolidFire",
|
||||||
|
TestData.tags: TestData.storageTag,
|
||||||
|
TestData.capacityIops: 4500000,
|
||||||
|
TestData.capacityBytes: 2251799813685248, # 2 PiB
|
||||||
|
TestData.hypervisor: "Any"
|
||||||
|
},
|
||||||
|
TestData.computeOffering: {
|
||||||
|
TestData.name: "SF_CO_1",
|
||||||
|
"displaytext": "SF_CO_1 (Min IOPS = 300; Max IOPS = 600)",
|
||||||
|
"cpunumber": 1,
|
||||||
|
"cpuspeed": 100,
|
||||||
|
"memory": 128,
|
||||||
|
"storagetype": "shared",
|
||||||
|
"customizediops": False,
|
||||||
|
"miniops": "300",
|
||||||
|
"maxiops": "600",
|
||||||
|
"hypervisorsnapshotreserve": 200,
|
||||||
|
TestData.tags: TestData.nfs_storage_tag
|
||||||
|
},
|
||||||
|
TestData.zoneId: 1,
|
||||||
|
TestData.podId: 1,
|
||||||
|
TestData.clusterId: 1,
|
||||||
|
TestData.domainId: 1,
|
||||||
|
TestData.url: "10.117.40.114"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestOnlineStorageMigration(cloudstackTestCase):
|
||||||
|
_should_only_be_one_volume_in_list_err_msg = "There should only be one volume in this list."
|
||||||
|
_volume_not_on_correct_primary_storage = "The volume is not on the correct primary storage."
|
||||||
|
_snapshot_not_associated_with_correct_volume = "The snapshot is not associated with the correct volume."
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
# Set up API client
|
||||||
|
testclient = super(TestOnlineStorageMigration, cls).getClsTestClient()
|
||||||
|
|
||||||
|
cls.apiClient = testclient.getApiClient()
|
||||||
|
cls.configData = testclient.getParsedTestDataConfig()
|
||||||
|
cls.dbConnection = testclient.getDbConnection()
|
||||||
|
|
||||||
|
cls.testdata = TestData().testdata
|
||||||
|
|
||||||
|
sf_util.set_supports_resign(True, cls.dbConnection)
|
||||||
|
|
||||||
|
cls._connect_to_hypervisor()
|
||||||
|
|
||||||
|
# Set up SolidFire connection
|
||||||
|
solidfire = cls.testdata[TestData.solidFire]
|
||||||
|
|
||||||
|
cls.sfe = ElementFactory.create(solidfire[TestData.mvip], solidfire[TestData.username], solidfire[TestData.password])
|
||||||
|
|
||||||
|
# Get Resources from Cloud Infrastructure
|
||||||
|
cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId])
|
||||||
|
cls.cluster = list_clusters(cls.apiClient)[0]
|
||||||
|
cls.template = get_template(cls.apiClient, cls.zone.id, hypervisor=TestData.hypervisor_type)
|
||||||
|
cls.domain = get_domain(cls.apiClient, cls.testdata[TestData.domainId])
|
||||||
|
|
||||||
|
# Create test account
|
||||||
|
cls.account = Account.create(
|
||||||
|
cls.apiClient,
|
||||||
|
cls.testdata["account"],
|
||||||
|
admin=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set up connection to make customized API calls
|
||||||
|
cls.user = User.create(
|
||||||
|
cls.apiClient,
|
||||||
|
cls.testdata["user"],
|
||||||
|
account=cls.account.name,
|
||||||
|
domainid=cls.domain.id
|
||||||
|
)
|
||||||
|
|
||||||
|
url = cls.testdata[TestData.url]
|
||||||
|
|
||||||
|
api_url = "http://" + url + ":8080/client/api"
|
||||||
|
userkeys = User.registerUserKeys(cls.apiClient, cls.user.id)
|
||||||
|
|
||||||
|
cls.cs_api = SignedAPICall.CloudStack(api_url, userkeys.apikey, userkeys.secretkey)
|
||||||
|
|
||||||
|
primarystorage = cls.testdata[TestData.primaryStorage]
|
||||||
|
|
||||||
|
cls.primary_storage = StoragePool.create(
|
||||||
|
cls.apiClient,
|
||||||
|
primarystorage,
|
||||||
|
scope=primarystorage[TestData.scope],
|
||||||
|
zoneid=cls.zone.id,
|
||||||
|
podid=cls.testdata[TestData.podId],
|
||||||
|
clusterid=cls.cluster.id,
|
||||||
|
tags=primarystorage[TestData.tags]
|
||||||
|
)
|
||||||
|
|
||||||
|
primarystorage2 = cls.testdata[TestData.primaryStorage2]
|
||||||
|
|
||||||
|
cls.primary_storage_2 = StoragePool.create(
|
||||||
|
cls.apiClient,
|
||||||
|
primarystorage2,
|
||||||
|
scope=primarystorage2[TestData.scope],
|
||||||
|
zoneid=cls.zone.id,
|
||||||
|
provider=primarystorage2[TestData.provider],
|
||||||
|
tags=primarystorage2[TestData.tags],
|
||||||
|
capacityiops=primarystorage2[TestData.capacityIops],
|
||||||
|
capacitybytes=primarystorage2[TestData.capacityBytes],
|
||||||
|
hypervisor=primarystorage2[TestData.hypervisor]
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.compute_offering = ServiceOffering.create(
|
||||||
|
cls.apiClient,
|
||||||
|
cls.testdata[TestData.computeOffering]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Resources that are to be destroyed
|
||||||
|
cls._cleanup = [
|
||||||
|
cls.compute_offering,
|
||||||
|
cls.user,
|
||||||
|
cls.account
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
try:
|
||||||
|
cleanup_resources(cls.apiClient, cls._cleanup)
|
||||||
|
|
||||||
|
cls.primary_storage.delete(cls.apiClient)
|
||||||
|
cls.primary_storage_2.delete(cls.apiClient)
|
||||||
|
|
||||||
|
ms = cls.testdata[TestData.managementServer]
|
||||||
|
ip_address = cls.testdata[TestData.url]
|
||||||
|
|
||||||
|
cls._delete_all_files(ip_address, ms[TestData.username], ms[TestData.password], TestData.nfs_folder)
|
||||||
|
|
||||||
|
sf_util.purge_solidfire_volumes(cls.sfe)
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug("Exception in tearDownClass(cls): %s" % e)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.cleanup = []
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
cleanup_resources(self.apiClient, self.cleanup)
|
||||||
|
|
||||||
|
def test_online_migrate_volume_from_nfs_storage_to_managed_storage(self):
|
||||||
|
if TestData.hypervisor_type != TestData.xenServer:
|
||||||
|
return
|
||||||
|
|
||||||
|
virtual_machine = VirtualMachine.create(
|
||||||
|
self.apiClient,
|
||||||
|
self._get_vm_name(),
|
||||||
|
accountid=self.account.name,
|
||||||
|
zoneid=self.zone.id,
|
||||||
|
serviceofferingid=self.compute_offering.id,
|
||||||
|
templateid=self.template.id,
|
||||||
|
domainid=self.domain.id,
|
||||||
|
startvm=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cleanup.append(virtual_machine)
|
||||||
|
|
||||||
|
vm_root_volume = self._get_only_volume(virtual_machine.id)
|
||||||
|
|
||||||
|
self._verify_volume_on_primary_storage(vm_root_volume, self.primary_storage)
|
||||||
|
|
||||||
|
# Migrate the root disk from NFS storage to managed storage.
|
||||||
|
|
||||||
|
Volume.migrate(self.apiClient, livemigrate=True, volumeid=vm_root_volume.id, storageid=self.primary_storage_2.id)
|
||||||
|
|
||||||
|
vm_root_volume = self._get_only_volume(virtual_machine.id)
|
||||||
|
|
||||||
|
self._verify_volume_on_primary_storage(vm_root_volume, self.primary_storage_2)
|
||||||
|
|
||||||
|
def test_online_migrate_volume_with_snapshot_from_nfs_storage_to_managed_storage(self):
|
||||||
|
if TestData.hypervisor_type != TestData.xenServer:
|
||||||
|
return
|
||||||
|
|
||||||
|
virtual_machine = VirtualMachine.create(
|
||||||
|
self.apiClient,
|
||||||
|
self._get_vm_name(),
|
||||||
|
accountid=self.account.name,
|
||||||
|
zoneid=self.zone.id,
|
||||||
|
serviceofferingid=self.compute_offering.id,
|
||||||
|
templateid=self.template.id,
|
||||||
|
domainid=self.domain.id,
|
||||||
|
startvm=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cleanup.append(virtual_machine)
|
||||||
|
|
||||||
|
vm_root_volume = self._get_only_volume(virtual_machine.id)
|
||||||
|
|
||||||
|
self._verify_volume_on_primary_storage(vm_root_volume, self.primary_storage)
|
||||||
|
|
||||||
|
vol_snap = Snapshot.create(
|
||||||
|
self.apiClient,
|
||||||
|
volume_id=vm_root_volume.id
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cleanup.append(vol_snap)
|
||||||
|
|
||||||
|
self._verify_snapshot_belongs_to_volume(vol_snap.id, vm_root_volume.id)
|
||||||
|
|
||||||
|
# Migrate the root disk from NFS storage to managed storage.
|
||||||
|
|
||||||
|
Volume.migrate(self.apiClient, livemigrate=True, volumeid=vm_root_volume.id, storageid=self.primary_storage_2.id)
|
||||||
|
|
||||||
|
vm_root_volume = self._get_only_volume(virtual_machine.id)
|
||||||
|
|
||||||
|
self._verify_volume_on_primary_storage(vm_root_volume, self.primary_storage_2)
|
||||||
|
|
||||||
|
self._verify_snapshot_belongs_to_volume(vol_snap.id, vm_root_volume.id)
|
||||||
|
|
||||||
|
def _verify_snapshot_belongs_to_volume(self, snapshot_id, volume_id):
|
||||||
|
snapshot = list_snapshots(
|
||||||
|
self.apiClient,
|
||||||
|
id=snapshot_id
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
snapshot.volumeid,
|
||||||
|
volume_id,
|
||||||
|
TestOnlineStorageMigration._snapshot_not_associated_with_correct_volume
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_only_volume(self, virtual_machine_id):
|
||||||
|
list_volumes_response = list_volumes(
|
||||||
|
self.apiClient,
|
||||||
|
virtualmachineid=virtual_machine_id,
|
||||||
|
listall=True
|
||||||
|
)
|
||||||
|
|
||||||
|
sf_util.check_list(list_volumes_response, 1, self, TestOnlineStorageMigration._should_only_be_one_volume_in_list_err_msg)
|
||||||
|
|
||||||
|
return list_volumes_response[0]
|
||||||
|
|
||||||
|
def _verify_volume_on_primary_storage(self, vm_root_volume, primary_storage):
|
||||||
|
self.assertEqual(
|
||||||
|
vm_root_volume.storageid,
|
||||||
|
primary_storage.id,
|
||||||
|
TestOnlineStorageMigration._volume_not_on_correct_primary_storage
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_vm_name(self):
|
||||||
|
number = random.randint(0, 1000)
|
||||||
|
|
||||||
|
vm_name = {
|
||||||
|
TestData.name: "VM-%d" % number,
|
||||||
|
"displayname": "Test VM %d" % number
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm_name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _delete_all_files(cls, ip_address, username, password, path):
|
||||||
|
ssh_connection = sf_util.get_ssh_connection(ip_address, username, password)
|
||||||
|
|
||||||
|
ssh_connection.exec_command("sudo rm -rf " + path + "/*")
|
||||||
|
|
||||||
|
ssh_connection.close()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _connect_to_hypervisor(cls):
|
||||||
|
host_ip = "https://" + \
|
||||||
|
list_hosts(cls.apiClient, clusterid=cls.testdata[TestData.clusterId], name=TestData.xen_server_hostname)[0].ipaddress
|
||||||
|
|
||||||
|
cls.xen_session = XenAPI.Session(host_ip)
|
||||||
|
|
||||||
|
xen_server = cls.testdata[TestData.xenServer]
|
||||||
|
|
||||||
|
cls.xen_session.xenapi.login_with_password(xen_server[TestData.username], xen_server[TestData.password])
|
||||||
Loading…
x
Reference in New Issue
Block a user