package org.ovirt.engine.core.bll.storage.lsm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.CommandBase;
import org.ovirt.engine.core.bll.InternalCommandAttribute;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
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.ExecutionHandler;
import org.ovirt.engine.core.bll.profiles.DiskProfileHelper;
import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaStorageConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaStorageDependent;
import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler;
import org.ovirt.engine.core.bll.tasks.CommandHelper;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskVmElementValidator;
import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.CreateAllSnapshotsFromVmParameters;
import org.ovirt.engine.core.common.action.LiveMigrateDiskParameters;
import org.ovirt.engine.core.common.action.LiveMigrateVmDisksParameters;
import org.ovirt.engine.core.common.action.LiveMigrateVmDisksParameters.LiveMigrateStage;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.RemoveSnapshotParameters;
import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.businessentities.ActionGroup;
import org.ovirt.engine.core.common.businessentities.Snapshot.SnapshotType;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.storage.Disk;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.DiskStorageType;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
import org.ovirt.engine.core.common.constants.StorageConstants;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.DiskDao;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.DiskVmElementDao;
import org.ovirt.engine.core.dao.ImageDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.utils.collections.MultiValueMapUtils;
@NonTransactiveCommandAttribute(forceCompensation = true)
@InternalCommandAttribute
public class LiveMigrateVmDisksCommand<T extends LiveMigrateVmDisksParameters> extends CommandBase<T>
implements QuotaStorageDependent, SerialChildExecutingCommand {
@Inject
private DiskProfileHelper diskProfileHelper;
@Inject
private DiskImageDao diskImageDao;
@Inject
private ImageDao imageDao;
@Inject
private DiskDao diskDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private DiskVmElementDao diskVmElementDao;
private Map<Guid, DiskImage> diskImagesMap = new HashMap<>();
private Map<Guid, StorageDomain> storageDomainsMap = new HashMap<>();
private Set<Guid> movedVmDiskIds;
public LiveMigrateVmDisksCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
getParameters().setCommandType(getActionType());
setVmId(getParameters().getVmId());
}
// ctor for compensation
public LiveMigrateVmDisksCommand(Guid commandId) {
super(commandId);
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
List<PermissionSubject> permissionList = new ArrayList<>();
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
DiskImage diskImage = diskImageDao.get(parameters.getImageId());
if (diskImage != null) {
permissionList.add(new PermissionSubject(diskImage.getId(),
VdcObjectType.Disk,
ActionGroup.DISK_LIVE_STORAGE_MIGRATION));
}
}
return permissionList;
}
private Set<Guid> getMovedDiskIds() {
if (movedVmDiskIds == null) {
movedVmDiskIds = new LinkedHashSet<>();
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
movedVmDiskIds.add(parameters.getImageGroupID());
}
}
return movedVmDiskIds;
}
@Override
protected void executeCommand() {
ImagesHandler.updateAllDiskImagesSnapshotsStatusInTransactionWithCompensation(getMovedDiskIds(),
ImageStatus.LOCKED,
ImageStatus.OK,
getCompensationContext());
VdcReturnValueBase vdcReturnValue = runInternalAction(VdcActionType.CreateAllSnapshotsFromVm,
getCreateSnapshotParameters(),
ExecutionHandler.createInternalJobContext(getContext()));
getParameters().setAutoGeneratedSnapshotId(vdcReturnValue.getActionReturnValue());
persistCommand(getParameters().getParentCommand(), getCallback() != null);
setSucceeded(true);
}
public CommandCallback getCallback() {
return new SerialChildCommandsExecutionCallback();
}
private void updateStage(LiveMigrateStage stage) {
getParameters().setStage(stage);
persistCommand(getParameters().getParentCommand(), getCallback() != null);
}
@Override
public boolean performNextOperation(int completedChildCount) {
if (getParameters().getStage() == LiveMigrateStage.CREATE_SNAPSHOT) {
updateStage(LiveMigrateStage.LIVE_MIGRATE_DISK_EXEC_START);
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
parameters.setSessionId(getParameters().getSessionId());
parameters.setEndProcedure(EndProcedure.COMMAND_MANAGED);
parameters.setParentCommand(getActionType());
parameters.setParentParameters(getParameters());
parameters.setDestinationImageId(((DiskImage)getDiskImageByDiskId(parameters.getImageGroupID()))
.getImageId());
VdcReturnValueBase vdcReturnValue =
runInternalAction(VdcActionType.LiveMigrateDisk,
parameters, ExecutionHandler.createInternalJobContext());
if (!vdcReturnValue.getSucceeded()) {
imageDao.updateStatusOfImagesByImageGroupId(parameters.getImageGroupID(), ImageStatus.OK);
}
}
updateStage(LiveMigrateStage.LIVE_MIGRATE_DISK_EXEC_COMPLETED);
return true;
}
if (isRemoveAutoGeneratedSnapshotRequired()) {
updateStage(LiveMigrateStage.AUTO_GENERATED_SNAPSHOT_REMOVE_START);
removeAutogeneratedSnapshot();
updateStage(LiveMigrateStage.AUTO_GENERATED_SNAPSHOT_REMOVE_END);
return true;
}
return false;
}
@Override
public boolean ignoreChildCommandFailure() {
return isRemoveAutoGeneratedSnapshotRequired();
}
private boolean isRemoveAutoGeneratedSnapshotRequired() {
boolean removeSnapshotRequired = getParameters().getStage() != LiveMigrateStage.CREATE_SNAPSHOT &&
getParameters().getStage() != LiveMigrateStage.AUTO_GENERATED_SNAPSHOT_REMOVE_END;
if (removeSnapshotRequired) {
if (!getVm().getStatus().isQualifiedForLiveSnapshotMerge()) {
// If the VM is not qualified for live merge, i.e. its status is not up, the auto-generated snapshot
// is not removed. Removing the snapshot while the VM isn't running will end up with cold merge
// and this is not desired here.
// Once cold merge enhanced to use qemu-img commit, this limit can be removed. See BZ 1246114.
// This behavior can be tracked by BZ 1369942.
log.warn("Auto-generated snapshot cannot be removed because VM isn't qualified for live merge. VM status is '{}'", getVm().getStatus());
removeSnapshotRequired = false;
}
}
return removeSnapshotRequired;
}
private void removeAutogeneratedSnapshot() {
RemoveSnapshotParameters removeSnapshotParameters = new RemoveSnapshotParameters(getParameters().getAutoGeneratedSnapshotId(),
getVmId());
removeSnapshotParameters.setEndProcedure(EndProcedure.COMMAND_MANAGED);
removeSnapshotParameters.setParentCommand(getActionType());
removeSnapshotParameters.setParentParameters(getParameters());
removeSnapshotParameters.setNeedsLocking(false);
runInternalAction(VdcActionType.RemoveSnapshot,
removeSnapshotParameters,
ExecutionHandler.createInternalJobContext(getContext()));
}
private List<DiskImage> getMovedDisks() {
Set<Guid> movedDiskIds = getMovedDiskIds();
List<DiskImage> disks = new ArrayList<>();
for (Guid diskId : movedDiskIds) {
DiskImage disk = new DiskImage();
disk.setId(diskId);
disks.add(disk);
}
return disks;
}
protected CreateAllSnapshotsFromVmParameters getCreateSnapshotParameters() {
CreateAllSnapshotsFromVmParameters params = new CreateAllSnapshotsFromVmParameters
(getParameters().getVmId(), StorageConstants.LSM_AUTO_GENERATED_SNAPSHOT_DESCRIPTION, false);
params.setParentCommand(VdcActionType.LiveMigrateVmDisks);
params.setSnapshotType(SnapshotType.REGULAR);
params.setParentParameters(getParameters());
params.setImagesParameters(getParameters().getImagesParameters());
params.setTaskGroupSuccess(getParameters().getTaskGroupSuccess());
params.setDisks(getMovedDisks());
params.setDiskIdsToIgnoreInChecks(getMovedDiskIds());
params.setNeedsLocking(false);
params.setEndProcedure(EndProcedure.COMMAND_MANAGED);
return params;
}
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
return Collections.singletonMap(getVmId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
Map<String, Pair<String, String>> locksMap = new HashMap<>();
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
locksMap.put(parameters.getImageGroupID().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK,
getDiskIsBeingMigratedMessage(getDiskImageByDiskId(parameters.getImageGroupID()))));
}
return locksMap;
}
private String getDiskIsBeingMigratedMessage(Disk disk) {
return EngineMessage.ACTION_TYPE_FAILED_DISK_IS_BEING_MIGRATED.name()
+ String.format("$DiskName %1$s", disk != null ? disk.getDiskAlias() : "");
}
@Override
public VM getVm() {
VM vm = super.getVm();
if (vm != null) {
setVm(vm);
}
return vm;
}
private DiskImage getDiskImageByImageId(Guid imageId) {
if (diskImagesMap.containsKey(imageId)) {
return diskImagesMap.get(imageId);
}
DiskImage diskImage = diskImageDao.get(imageId);
diskImagesMap.put(imageId, diskImage);
return diskImage;
}
private Disk getDiskImageByDiskId(Guid diskId) {
Disk disk = diskDao.get(diskId);
if (disk != null && disk.getDiskStorageType() == DiskStorageType.IMAGE) {
DiskImage diskImage = (DiskImage)disk;
if (!diskImagesMap.containsKey(diskImage.getImageId())) {
diskImagesMap.put(diskImage.getImageId(), (DiskImage)disk);
}
}
return disk;
}
private StorageDomain getStorageDomainById(Guid storageDomainId, Guid storagePoolId) {
if (storageDomainsMap.containsKey(storageDomainId)) {
return storageDomainsMap.get(storageDomainId);
}
StorageDomain storageDomain = storageDomainDao.getForStoragePool(storageDomainId, storagePoolId);
storageDomainsMap.put(storageDomainId, storageDomain);
return storageDomain;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__MOVE);
addValidationMessage(EngineMessage.VAR__TYPE__DISK);
}
protected boolean setAndValidateDiskProfiles() {
Map<DiskImage, Guid> map = new HashMap<>();
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
DiskImage diskImage = getDiskImageByImageId(parameters.getImageId());
map.put(diskImage, diskImage.getStorageIds().get(0));
}
return validate(diskProfileHelper.setAndValidateDiskProfiles(map, getCurrentUser()));
}
@Override
public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() {
List<QuotaConsumptionParameter> list = new ArrayList<>();
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
DiskImage diskImage = getDiskImageByImageId(parameters.getImageId());
list.add(new QuotaStorageConsumptionParameter(
parameters.getQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.CONSUME,
parameters.getTargetStorageDomainId(),
(double) diskImage.getSizeInGigabytes()));
if (diskImage.getQuotaId() != null && !Guid.Empty.equals(diskImage.getQuotaId())) {
list.add(new QuotaStorageConsumptionParameter(
diskImage.getQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.RELEASE,
parameters.getSourceDomainId(),
(double) diskImage.getSizeInGigabytes()));
}
}
return list;
}
@Override
protected boolean validate() {
setStoragePoolId(getVm().getStoragePoolId());
if (!isValidParametersList() || !validateDestDomainsSpaceRequirements()) {
return false;
}
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
getReturnValue().setValid(isDiskNotShareable(parameters.getImageId())
&& isDiskSnapshotNotPluggedToOtherVmsThatAreNotDown(parameters.getImageId())
&& isTemplateInDestStorageDomain(parameters.getImageId(), parameters.getTargetStorageDomainId())
&& validateDestStorage(getStorageDomainById(parameters.getTargetStorageDomainId(), getStoragePoolId()))
&& isSameSourceAndDest(parameters)
&& validatePassDiscardSupportedOnDestinationStorageDomain(parameters));
if (!getReturnValue().isValid()) {
return false;
}
}
if (!setAndValidateDiskProfiles()) {
return false;
}
return validateCreateAllSnapshotsFromVmCommand();
}
private boolean isValidParametersList() {
if (getParameters().getParametersList().isEmpty()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_NO_DISKS_SPECIFIED);
}
return true;
}
private boolean isSameSourceAndDest(LiveMigrateDiskParameters parameters) {
if (parameters.getSourceStorageDomainId().equals(parameters.getTargetStorageDomainId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_SOURCE_AND_TARGET_SAME);
}
return true;
}
protected boolean validatePassDiscardSupportedOnDestinationStorageDomain(LiveMigrateDiskParameters parameters) {
DiskVmElementValidator validator =
createDiskVmElementValidator(parameters.getImageGroupID(), parameters.getVmId());
return validate(validator.isPassDiscardSupported(parameters.getTargetStorageDomainId()));
}
protected DiskVmElementValidator createDiskVmElementValidator(Guid diskId, Guid vmId) {
return new DiskVmElementValidator(diskDao.get(diskId), diskVmElementDao.get(new VmDeviceId(diskId, vmId)));
}
private boolean isDiskNotShareable(Guid imageId) {
DiskImage diskImage = getDiskImageByImageId(imageId);
if (diskImage.isShareable()) {
addValidationMessageVariable("diskAliases", diskImage.getDiskAlias());
return failValidation(EngineMessage.ACTION_TYPE_FAILED_SHAREABLE_DISK_NOT_SUPPORTED);
}
return true;
}
private boolean isTemplateInDestStorageDomain(Guid imageId, Guid sourceDomainId) {
Guid templateId = getDiskImageByImageId(imageId).getImageTemplateId();
if (!Guid.Empty.equals(templateId)) {
DiskImage templateImage = diskImageDao.get(templateId);
if (!templateImage.getStorageIds().contains(sourceDomainId)) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_TEMPLATE_NOT_FOUND_ON_DESTINATION_DOMAIN);
}
}
return true;
}
private boolean validateDestStorage(StorageDomain destDomain) {
StorageDomainValidator validator = new StorageDomainValidator(destDomain);
return validate(validator.isDomainExistAndActive()) && validate(validator.domainIsValidDestination());
}
protected boolean validateDestDomainsSpaceRequirements() {
Map<Guid, List<DiskImage>> storageDomainsImagesMap = new HashMap<>();
for (LiveMigrateDiskParameters parameters : getParameters().getParametersList()) {
MultiValueMapUtils.addToMap(parameters.getTargetStorageDomainId(),
getDiskImageByImageId(parameters.getImageId()),
storageDomainsImagesMap);
}
for (Map.Entry<Guid, List<DiskImage>> entry : storageDomainsImagesMap.entrySet()) {
Guid destDomainId = entry.getKey();
List<DiskImage> disksList = entry.getValue();
Guid storagePoolId = disksList.get(0).getStoragePoolId();
StorageDomain destDomain = getStorageDomainById(destDomainId, storagePoolId);
if (!isStorageDomainWithinThresholds(destDomain)) {
return false;
}
for (DiskImage diskImage : disksList) {
List<DiskImage> allImageSnapshots = diskImageDao.getAllSnapshotsForLeaf(diskImage.getImageId());
diskImage.getSnapshots().addAll(allImageSnapshots);
}
StorageDomainValidator storageDomainValidator = createStorageDomainValidator(destDomain);
if (!validate(storageDomainValidator.hasSpaceForClonedDisks(disksList))) {
return false;
}
}
return true;
}
protected boolean isDiskSnapshotNotPluggedToOtherVmsThatAreNotDown(Guid imageId) {
return validate(createDiskValidator(getDiskImageByImageId(imageId)).isDiskPluggedToVmsThatAreNotDown(true, null));
}
protected boolean isStorageDomainWithinThresholds(StorageDomain storageDomain) {
return validate(new StorageDomainValidator(storageDomain).isDomainWithinThresholds());
}
protected VmValidator createVmValidator() {
return new VmValidator(getVm());
}
protected DiskValidator createDiskValidator(Disk disk) {
return new DiskValidator(disk);
}
protected StorageDomainValidator createStorageDomainValidator(StorageDomain storageDomain) {
return new StorageDomainValidator(storageDomain);
}
protected boolean validateCreateAllSnapshotsFromVmCommand() {
VdcReturnValueBase returnValue = CommandHelper.validate(VdcActionType.CreateAllSnapshotsFromVm,
getCreateSnapshotParameters(), getContext().clone());
if (!returnValue.isValid()) {
getReturnValue().setValidationMessages(returnValue.getValidationMessages());
return false;
}
return true;
}
}