package org.ovirt.engine.core.bll.storage.lsm;
import java.util.Collections;
import java.util.Map;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.CommandActionState;
import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
import org.ovirt.engine.core.bll.SerialChildCommandsExecutionCallback;
import org.ovirt.engine.core.bll.SerialChildExecutingCommand;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.job.ExecutionContext;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.bll.storage.disk.MoveOrCopyDiskCommand;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.validator.storage.DiskValidator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.action.CreateImagePlaceholderCommandParameters;
import org.ovirt.engine.core.common.action.LiveMigrateDiskParameters;
import org.ovirt.engine.core.common.action.LiveMigrateDiskParameters.LiveDiskMigrateStage;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.SyncImageGroupDataCommandParameters;
import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskImageDynamic;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.businessentities.storage.ImageStorageDomainMap;
import org.ovirt.engine.core.common.businessentities.storage.ImageStorageDomainMapId;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.job.Step;
import org.ovirt.engine.core.common.job.StepEnum;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.GetImageInfoVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.common.vdscommands.VmReplicateDiskParameters;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import org.ovirt.engine.core.dal.job.ExecutionMessageDirector;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.DiskImageDynamicDao;
import org.ovirt.engine.core.dao.ImageDao;
import org.ovirt.engine.core.dao.ImageStorageDomainMapDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.ovirt.engine.core.vdsbroker.ResourceManager;
@NonTransactiveCommandAttribute
public class LiveMigrateDiskCommand<T extends LiveMigrateDiskParameters> extends MoveOrCopyDiskCommand<T>implements SerialChildExecutingCommand {
private Guid sourceQuotaId;
private Guid sourceDiskProfileId;
@Inject
private LiveStorageMigrationHelper liveStorageMigrationHelper;
@Inject
private ResourceManager resourceManager;
@Inject
private ImageDao imageDao;
@Inject
private ImageStorageDomainMapDao imageStorageDomainMapDao;
@Inject
private DiskImageDynamicDao diskImageDynamicDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmDao vmDao;
public LiveMigrateDiskCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
@Override
public void init() {
super.init();
setStoragePoolId(getVm().getStoragePoolId());
getParameters().setStoragePoolId(getStoragePoolId());
getParameters().setVdsId(getVdsId());
getParameters().setDiskAlias(getDiskAlias());
getParameters().setImageGroupID(getImageGroupId());
getParameters().setCommandType(getActionType());
}
private CreateImagePlaceholderCommandParameters buildCreateImagePlacerholderParams() {
CreateImagePlaceholderCommandParameters p = new CreateImagePlaceholderCommandParameters(
getParameters().getStoragePoolId(),
getParameters().getImageGroupID(),
getParameters().getSourceStorageDomainId(),
getParameters().getTargetStorageDomainId());
p.setParentCommand(getActionType());
p.setParentParameters(getParameters());
p.setEndProcedure(EndProcedure.COMMAND_MANAGED);
return p;
}
public CommandCallback getCallback() {
return new SerialChildCommandsExecutionCallback();
}
@Override
protected void executeCommand() {
runInternalAction(VdcActionType.CreateImagePlaceholder,
buildCreateImagePlacerholderParams(), createStepsContext(StepEnum.CLONE_IMAGE_STRUCTURE));
setSucceeded(true);
}
@Override
public boolean performNextOperation(int completedChildCount) {
if (getParameters().getLiveDiskMigrateStage() == LiveDiskMigrateStage.IMAGE_PLACEHOLDER_CREATION) {
updateStage(LiveDiskMigrateStage.VM_REPLICATE_DISK_START);
replicateDiskStart();
updateStage(LiveDiskMigrateStage.IMAGE_DATA_SYNC_EXEC_START);
syncImageData();
updateStage(LiveDiskMigrateStage.IMAGE_DATA_SYNC_EXEC_END);
return true;
}
if (getParameters().getLiveDiskMigrateStage() == LiveDiskMigrateStage.IMAGE_DATA_SYNC_EXEC_END) {
updateStage(LiveDiskMigrateStage.VM_REPLICATE_DISK_FINISH);
completeLiveMigration();
updateStage(LiveDiskMigrateStage.SOURCE_IMAGE_DELETION);
liveStorageMigrationHelper.removeImage(this, getParameters().getSourceStorageDomainId(), getParameters()
.getImageGroupID(), getParameters().getDestinationImageId(), AuditLogType
.USER_MOVE_IMAGE_GROUP_FAILED_TO_DELETE_SRC_IMAGE);
return false;
}
return false;
}
private CommandContext createStepsContext(StepEnum step) {
Step addedStep = executionHandler.addSubStep(getExecutionContext(),
getExecutionContext().getJob().getStep(StepEnum.EXECUTING),
step,
ExecutionMessageDirector.resolveStepMessage(step, Collections.emptyMap()));
ExecutionContext ctx = new ExecutionContext();
ctx.setStep(addedStep);
ctx.setMonitored(true);
CommandContext commandCtx = ExecutionHandler.createDefaultContextForTasks(getContext(), null)
.withExecutionContext(ctx);
return commandCtx;
}
private void unlockDisk() {
imageDao.updateStatusOfImagesByImageGroupId(getParameters().getImageGroupID(), ImageStatus.OK);
}
private boolean isConsiderSuccessful() {
return getParameters().getLiveDiskMigrateStage() == LiveDiskMigrateStage.SOURCE_IMAGE_DELETION;
}
@Override
protected void endSuccessfully() {
super.endSuccessfully();
updateImagesInfo();
unlockDisk();
setSucceeded(true);
}
@Override
protected void endWithFailure() {
// if we failed to removed the source image, we should add an audit log and consider the
// operation as successful.
if (isConsiderSuccessful()) {
auditLog(this, AuditLogType.USER_MOVE_IMAGE_GROUP_FAILED_TO_DELETE_SRC_IMAGE);
this.endSuccessfully();
return;
}
super.endWithFailure();
handleDestDisk();
unlockDisk();
setSucceeded(true);
}
private void completeLiveMigration() {
// Update the DB before sending the command (perform rollback on failure)
moveDiskInDB(getParameters().getSourceStorageDomainId(),
getParameters().getTargetStorageDomainId(),
getParameters().getQuotaId(),
getParameters().getDiskProfileId());
try {
replicateDiskFinish(getParameters().getSourceStorageDomainId(),
getParameters().getTargetStorageDomainId());
} catch (Exception e) {
moveDiskInDB(getParameters().getTargetStorageDomainId(),
getParameters().getSourceStorageDomainId(),
sourceQuotaId,
sourceDiskProfileId);
log.error("Failed VmReplicateDiskFinish (Disk '{}', VM '{}')",
getParameters().getImageGroupID(),
getParameters().getVmId());
throw e;
}
}
private void replicateDiskFinish(Guid srcDomain, Guid dstDomain) {
VmReplicateDiskParameters migrationStartParams = new VmReplicateDiskParameters(getParameters().getVdsId(),
getParameters().getVmId(),
getParameters().getStoragePoolId(),
srcDomain,
dstDomain,
getParameters().getImageGroupID(),
getParameters().getDestinationImageId());
VDSReturnValue ret = resourceManager.runVdsCommand(
VDSCommandType.VmReplicateDiskFinish, migrationStartParams);
if (!ret.getSucceeded()) {
throw new EngineException(ret.getVdsError().getCode(), ret.getVdsError().getMessage());
}
}
private boolean isMoveDiskInDbSucceded(Guid targetStorageDomainId) {
Guid destinationImageId = getParameters().getDestinationImageId();
DiskImage diskImage = diskImageDao.get(destinationImageId);
return diskImage != null && targetStorageDomainId.equals(diskImage.getStorageIds().get(0));
}
private void moveDiskInDB(final Guid sourceStorageDomainId,
final Guid targetStorageDomainId,
final Guid targetQuota,
final Guid targetDiskProfile) {
if (isMoveDiskInDbSucceded(targetStorageDomainId)) {
return;
}
TransactionSupport.executeInScope(TransactionScopeOption.Required,
() -> {
for (DiskImage di : diskImageDao.getAllSnapshotsForImageGroup(getParameters().getImageGroupID())) {
imageStorageDomainMapDao.remove(new ImageStorageDomainMapId(di.getImageId(),
sourceStorageDomainId));
imageStorageDomainMapDao.save(new ImageStorageDomainMap(di.getImageId(),
targetStorageDomainId,
targetQuota,
targetDiskProfile));
// since moveDiskInDB can be called to 'rollback' the entity in case of
// an exception, we store locally the old quota and disk profile id.
if (sourceQuotaId == null) {
sourceQuotaId = di.getQuotaId();
}
if (sourceDiskProfileId == null) {
sourceDiskProfileId = di.getDiskProfileId();
}
}
return null;
});
}
private void updateImagesInfo() {
for (DiskImage image : diskImageDao.getAllSnapshotsForImageGroup(getParameters().getImageGroupID())) {
VDSReturnValue ret = runVdsCommand(
VDSCommandType.GetImageInfo,
new GetImageInfoVDSCommandParameters(getParameters().getStoragePoolId(),
getParameters().getTargetStorageDomainId(),
getParameters().getImageGroupID(),
image.getImageId()));
DiskImage imageFromIRS = (DiskImage) ret.getReturnValue();
setQcowCompatForSnapshot(image, imageFromIRS);
DiskImageDynamic diskImageDynamic = diskImageDynamicDao.get(image.getImageId());
// Update image's actual size in DB
if (imageFromIRS != null && diskImageDynamic != null) {
diskImageDynamic.setActualSize(imageFromIRS.getActualSizeInBytes());
diskImageDynamicDao.update(diskImageDynamic);
}
}
}
private void syncImageData() {
SyncImageGroupDataCommandParameters parameters =
new SyncImageGroupDataCommandParameters(getParameters().getStoragePoolId(),
getParameters().getImageGroupID(),
getParameters().getSourceStorageDomainId(),
getParameters().getTargetStorageDomainId());
parameters.setEndProcedure(EndProcedure.COMMAND_MANAGED);
parameters.setParentCommand(getActionType());
parameters.setParentParameters(getParameters());
runInternalAction(VdcActionType.SyncImageGroupData, parameters, createStepsContext(StepEnum.SYNC_IMAGE_DATA));
}
private void replicateDiskStart() {
if (Guid.Empty.equals(getParameters().getVdsId())) {
throw new EngineException(EngineError.down,
"VM " + getParameters().getVmId() + " is not running on any VDS");
}
// Start disk migration
VmReplicateDiskParameters migrationStartParams = new VmReplicateDiskParameters(getParameters().getVdsId(),
getParameters().getVmId(),
getParameters().getStoragePoolId(),
getParameters().getSourceStorageDomainId(),
getParameters().getTargetStorageDomainId(),
getParameters().getImageGroupID(),
getParameters().getDestinationImageId());
VDSReturnValue ret = resourceManager.runVdsCommand(VDSCommandType.VmReplicateDiskStart, migrationStartParams);
if (!ret.getSucceeded()) {
log.error("Failed VmReplicateDiskStart (Disk '{}' , VM '{}')",
getParameters().getImageGroupID(),
getParameters().getVmId());
throw new EngineException(ret.getVdsError().getCode(), ret.getVdsError().getMessage());
}
}
private void updateStage(LiveDiskMigrateStage stage) {
getParameters().setLiveDiskMigrateStage(stage);
persistCommand(getParameters().getParentCommand(), getCallback() != null);
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
@Override
protected boolean checkCanBeMoveInVm() {
return validate(createDiskValidator(getDiskImage()).isDiskPluggedToVmsThatAreNotDown(true,
getVmsWithVmDeviceInfoForDiskId()));
}
@Override
public VM getVm() {
VM vm = super.getVm();
if (vm == null) {
vm = vmDao.getVmsListForDisk(getImageGroupId(), false).get(0);
setVm(vm);
setVmId(vm.getId());
}
return vm;
}
@Override
public Guid getVdsId() {
return getVm().getRunOnVds() != null ? getVm().getRunOnVds() : Guid.Empty;
}
public Guid persistAsyncTaskPlaceHolder() {
return super.persistAsyncTaskPlaceHolder(getActionType());
}
public Guid persistAsyncTaskPlaceHolder(String taskKey) {
return super.persistAsyncTaskPlaceHolder(getActionType(), taskKey);
}
@Override
protected boolean validate() {
boolean validate = super.validate();
if (!validate) {
auditLogDirector.log(this, AuditLogType.USER_MOVED_DISK_FINISHED_FAILURE);
}
return validate;
}
@Override
protected boolean isImageNotLocked() {
// During LSM the disks are being locked prior to the snapshot phase
// therefore returning true here.
return true;
}
protected DiskValidator createDiskValidator(DiskImage disk) {
return new DiskValidator(disk);
}
@Override
public AuditLogType getAuditLogTypeValue() {
if (getActionState() == CommandActionState.EXECUTE) {
if (!getParameters().getTaskGroupSuccess()) {
return AuditLogType.USER_MOVED_DISK_FINISHED_FAILURE;
}
if (getSucceeded()) {
return AuditLogType.USER_MOVED_DISK;
}
} else if (getActionState() == CommandActionState.END_SUCCESS || isConsiderSuccessful()) {
return AuditLogType.USER_MOVED_DISK_FINISHED_SUCCESS;
} else {
return AuditLogType.USER_MOVED_DISK_FINISHED_FAILURE;
}
return AuditLogType.UNASSIGNED;
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
return null;
}
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
return null;
}
private void handleDestDisk() {
if (getParameters().getLiveDiskMigrateStage() != LiveDiskMigrateStage.IMAGE_PLACEHOLDER_CREATION &&
getParameters().getLiveDiskMigrateStage() != LiveDiskMigrateStage.SOURCE_IMAGE_DELETION) {
if (Guid.Empty.equals(getParameters().getVdsId())) {
log.error("Failed during live storage migration of disk '{}' of vm '{}', as the vm is not running" +
" on any host not attempting to end the replication before the target disk deletion",
getParameters().getImageGroupID(), getParameters().getVmId());
} else {
log.error("Failed during live storage migration of disk '{}' of vm '{}', attempting to end " +
"replication before deleting the target disk",
getParameters().getImageGroupID(), getParameters().getVmId());
try {
replicateDiskFinish(getParameters().getSourceStorageDomainId(),
getParameters().getSourceStorageDomainId());
} catch (Exception e) {
log.error("Replication end of disk '{}' in vm '{}' back to the source failed, skipping deletion of " +
"the target disk", getParameters().getImageGroupID(), getParameters().getVmId());
return;
}
}
log.error("Attempting to delete the target of disk '{}' of vm '{}'",
getParameters().getImageGroupID(), getParameters().getVmId());
liveStorageMigrationHelper.removeImage(this, getParameters().getTargetStorageDomainId(), getParameters()
.getImageGroupID(), getParameters().getDestinationImageId(), AuditLogType
.USER_MOVE_IMAGE_GROUP_FAILED_TO_DELETE_DST_IMAGE);
}
}
}