package org.ovirt.engine.core.bll.storage.disk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.ConcurrentChildCommandsExecutionCallback;
import org.ovirt.engine.core.bll.DisableInPrepareMode;
import org.ovirt.engine.core.bll.LockMessage;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
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.snapshots.SnapshotsValidator;
import org.ovirt.engine.core.bll.storage.disk.image.CopyImageGroupCommand;
import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.storage.DiskValidator;
import org.ovirt.engine.core.bll.validator.storage.MultipleDiskVmElementValidator;
import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.MoveOrCopyImageGroupParameters;
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.StorageDomain;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.VmEntityType;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.storage.CopyVolumeType;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.ImageDbOperationScope;
import org.ovirt.engine.core.common.businessentities.storage.ImageOperation;
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.StorageType;
import org.ovirt.engine.core.common.businessentities.storage.UnregisteredDisk;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.job.Job;
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.DiskImageDao;
import org.ovirt.engine.core.dao.DiskVmElementDao;
import org.ovirt.engine.core.dao.ImageStorageDomainMapDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.UnregisteredDisksDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.dao.VmTemplateDao;
@DisableInPrepareMode
@NonTransactiveCommandAttribute
public class MoveOrCopyDiskCommand<T extends MoveOrCopyImageGroupParameters> extends CopyImageGroupCommand<T>
implements QuotaStorageDependent {
@Inject
private DiskProfileHelper diskProfileHelper;
@Inject
private ImageStorageDomainMapDao imageStorageDomainMapDao;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private DiskVmElementDao diskVmElementDao;
@Inject
private VmTemplateDao vmTemplateDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private SnapshotsValidator snapshotsValidator;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private UnregisteredDisksDao unregisteredDisksDao;
@Inject
private VmDao vmDao;
private List<PermissionSubject> cachedPermsList;
private List<Pair<VM, VmDevice>> cachedVmsDeviceInfo;
private String cachedDiskIsBeingMigratedMessage;
public MoveOrCopyDiskCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
@Override
protected DiskImage getImage() {
return super.getImage();
}
@Override
protected Guid getImageGroupId() {
return super.getImageGroupId();
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
@Override
public void init() {
super.init();
if (isCopyOperation()) {
setVmTemplate(getTemplateForImage());
}
}
protected VmTemplate getTemplateForImage() {
if (getImage() == null) {
return null;
}
return vmTemplateDao.getAllForImage(getImage().getImageId()).values().stream().findAny().orElse(null);
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(isCopyOperation() ?
EngineMessage.VAR__ACTION__COPY
: EngineMessage.VAR__ACTION__MOVE);
addValidationMessage(EngineMessage.VAR__TYPE__DISK);
}
@Override
protected boolean validate() {
return super.validate()
&& isImageExist()
&& checkOperationIsCorrect()
&& isDiskUsedAsOvfStore()
&& isImageNotLocked()
&& isSourceAndDestTheSame()
&& validateSourceStorageDomain()
&& validateDestStorage()
&& checkTemplateInDestStorageDomain()
&& validateSpaceRequirements()
&& validateVmSnapshotStatus()
&& checkCanBeMoveInVm()
&& checkIfNeedToBeOverride()
&& setAndValidateDiskProfiles()
&& validatePassDiscardSupportedForDestinationStorageDomain();
}
protected boolean isSourceAndDestTheSame() {
if (isMoveOperation()
&& getParameters().getSourceDomainId().equals(getParameters().getStorageDomainId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_SOURCE_AND_TARGET_SAME);
}
return true;
}
protected boolean isImageExist() {
if (getImage() == null) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_DISK_NOT_EXIST);
}
return true;
}
protected boolean isImageNotLocked() {
DiskImage diskImage = getImage();
if (diskImage.getImageStatus() == ImageStatus.LOCKED) {
if (isMoveOperation()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_DISKS_LOCKED,
String.format("$%1$s %2$s", "diskAliases", diskImage.getDiskAlias()));
} else {
return failValidation(EngineMessage.VM_TEMPLATE_IMAGE_IS_LOCKED);
}
}
return true;
}
protected boolean isDiskUsedAsOvfStore() {
return validate(createDiskValidator().isDiskUsedAsOvfStore());
}
/**
* The following method will perform a check for correctness of operation
* It is allow to move only if it is image of template
*/
protected boolean checkOperationIsCorrect() {
if (isMoveOperation() && getImage().getVmEntityType() != null && getImage().getVmEntityType().isTemplateType()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_DISK_IS_NOT_VM_DISK,
String.format("$%1$s %2$s", "diskAliases", getImage().getDiskAlias()));
}
return true;
}
protected boolean validateDestStorage() {
StorageDomainValidator validator = new StorageDomainValidator(getStorageDomain());
if (!validate(validator.isDomainExistAndActive()) || !validate(validator.domainIsValidDestination())) {
return false;
}
// Validate shareable disks moving/copying
boolean moveOrCopy = isMoveOperation() || isCopyOperation();
if (moveOrCopy && getImage().isShareable() && getStorageDomain().getStorageType() == StorageType.GLUSTERFS ) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_CANT_MOVE_SHAREABLE_DISK_TO_GLUSTERFS,
String.format("$%1$s %2$s", "diskAlias", getImage().getDiskAlias()));
}
return true;
}
/**
* Check if destination storage has enough space
*/
protected boolean validateSpaceRequirements() {
if (isUnregisteredDiskExistsForCopyTemplate()) {
return true;
}
StorageDomainValidator storageDomainValidator = createStorageDomainValidator();
if (validate(storageDomainValidator.isDomainWithinThresholds())) {
// If we are copying a template's disk we do not want all its copies
if (getImage().getVmEntityType() == VmEntityType.TEMPLATE) {
getImage().getSnapshots().add(getImage());
} else {
getImage().getSnapshots().addAll(diskImageDao.getAllSnapshotsForLeaf(getImage().getImageId()));
}
return validate(storageDomainValidator.hasSpaceForDiskWithSnapshots(getImage()));
}
return false;
}
private boolean validateVmSnapshotStatus() {
for (Pair<VM, VmDevice> pair : getVmsWithVmDeviceInfoForDiskId()) {
VmDevice vmDevice = pair.getSecond();
if (vmDevice.getSnapshotId() == null) { // Skip check for VMs with connected snapshot
VM vm = pair.getFirst();
if (!validate(snapshotsValidator.vmNotInPreview(vm.getId()))) {
return false;
}
}
}
return true;
}
protected boolean checkIfNeedToBeOverride() {
if (isTemplate() && isCopyOperation() && !getParameters().getForceOverride() &&
getImage().getStorageIds().contains(getStorageDomain().getId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_IMAGE_ALREADY_EXISTS);
}
return true;
}
/**
* Validate a source storage domain of image, when a source storage domain is not provided
* any of the domains image will be used
*/
protected boolean validateSourceStorageDomain() {
Guid sourceDomainId = getParameters().getSourceDomainId();
if (sourceDomainId == null || Guid.Empty.equals(sourceDomainId)) {
sourceDomainId = getImage().getStorageIds().get(0);
getParameters().setSourceDomainId(sourceDomainId);
}
StorageDomainValidator validator =
new StorageDomainValidator(storageDomainDao.getForStoragePool(sourceDomainId,
getImage().getStoragePoolId()));
return validate(validator.isDomainExistAndActive());
}
/**
* If a disk is attached to VM it can be moved when it is unplugged or at case that disk is plugged
* vm should be down
*/
protected boolean checkCanBeMoveInVm() {
return validate(createDiskValidator().isDiskPluggedToVmsThatAreNotDown(false, getVmsWithVmDeviceInfoForDiskId()));
}
/**
* Cache method to retrieve all the VMs with the device info related to the image
*/
protected List<Pair<VM, VmDevice>> getVmsWithVmDeviceInfoForDiskId() {
if (cachedVmsDeviceInfo == null) {
cachedVmsDeviceInfo = vmDao.getVmsWithPlugInfo(getImage().getId());
}
return cachedVmsDeviceInfo;
}
/**
* The following method will check, if we can move disk to destination storage domain, when
* it is based on template
*/
protected boolean checkTemplateInDestStorageDomain() {
if (isMoveOperation()
&& !Guid.Empty.equals(getImage().getImageTemplateId())) {
DiskImage templateImage = diskImageDao.get(getImage().getImageTemplateId());
if (!templateImage.getStorageIds().contains(getParameters().getStorageDomainId())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_TEMPLATE_NOT_FOUND_ON_DESTINATION_DOMAIN);
}
}
return true;
}
protected VdcActionType getImagesActionType() {
if (isMoveOperation()) {
return VdcActionType.MoveImageGroup;
}
return VdcActionType.CopyImageGroup;
}
@Override
protected void executeCommand() {
if (isUnregisteredDiskExistsForCopyTemplate()) {
addDiskMapping();
return;
}
MoveOrCopyImageGroupParameters p = prepareChildParameters();
VdcReturnValueBase vdcRetValue = runInternalActionWithTasksContext(
getImagesActionType(),
p);
if (!vdcRetValue.getSucceeded()) {
setSucceeded(false);
getReturnValue().setFault(vdcRetValue.getFault());
} else {
setSucceeded(true);
if (isCopyOperation() && !isTemplate()) {
ImagesHandler.addDiskImageWithNoVmDevice(getImage());
}
}
}
private void addDiskMapping() {
executeInNewTransaction(() -> {
addStorageDomainMapForCopiedTemplateDisk();
unregisteredDisksDao.removeUnregisteredDisk(getImage().getId(), getParameters().getStorageDomainId());
incrementDbGenerationForRelatedEntities();
return null;
});
setSucceeded(true);
}
@Override
public CommandCallback getCallback() {
return new ConcurrentChildCommandsExecutionCallback();
}
protected boolean isUnregisteredDiskExistsForCopyTemplate() {
if (isTemplate() && isCopyOperation()) {
List<UnregisteredDisk> unregisteredDisks =
unregisteredDisksDao.getByDiskIdAndStorageDomainId(getImage().getId(),
getParameters().getStorageDomainId());
if (!unregisteredDisks.isEmpty()) {
return true;
}
}
return false;
}
private void addStorageDomainMapForCopiedTemplateDisk() {
imageStorageDomainMapDao.save
(new ImageStorageDomainMap(getParameters().getImageId(),
getParameters().getStorageDomainId(),
getParameters().getQuotaId(),
getImage().getDiskProfileId()));
}
private void endCommandActions() {
if (!getParameters().getImagesParameters().isEmpty()) {
getBackend().endAction(getImagesActionType(),
getParameters().getImagesParameters().get(0),
ExecutionHandler.createDefaultContextForTasks(getContext()));
}
setSucceeded(true);
}
@Override
protected void endSuccessfully() {
endCommandActions();
incrementDbGenerationForRelatedEntities();
}
private void incrementDbGenerationForRelatedEntities() {
if (isCopyOperation()) {
// When copying a non template disk the copy is to a new
// floating disk, so no need to increment any generations.
if (!isTemplate()) {
return;
}
vmStaticDao.incrementDbGeneration(getVmTemplateId());
} else {
getVmsWithVmDeviceInfoForDiskId().stream().forEach(p -> vmStaticDao.incrementDbGeneration(p.getFirst().getId()));
}
}
@Override
protected void endWithFailure() {
endCommandActions();
}
@Override
public AuditLogType getAuditLogTypeValue() {
switch (getActionState()) {
case EXECUTE:
return getSucceeded() ? (isMoveOperation()) ? AuditLogType.USER_MOVED_DISK
: AuditLogType.USER_COPIED_DISK
: (isMoveOperation()) ? AuditLogType.USER_FAILED_MOVED_VM_DISK
: AuditLogType.USER_FAILED_COPY_DISK;
case END_SUCCESS:
return getSucceeded() ? (isMoveOperation()) ? AuditLogType.USER_MOVED_DISK_FINISHED_SUCCESS
: AuditLogType.USER_COPIED_DISK_FINISHED_SUCCESS
: (isMoveOperation()) ? AuditLogType.USER_MOVED_DISK_FINISHED_FAILURE
: AuditLogType.USER_COPIED_DISK_FINISHED_FAILURE;
default:
return (isMoveOperation()) ? AuditLogType.USER_MOVED_DISK_FINISHED_FAILURE
: AuditLogType.USER_COPIED_DISK_FINISHED_FAILURE;
}
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
if (cachedPermsList == null) {
cachedPermsList = new ArrayList<>();
DiskImage image = getImage();
Guid diskId = image == null ? Guid.Empty : image.getId();
cachedPermsList.add(new PermissionSubject(diskId, VdcObjectType.Disk, ActionGroup.CONFIGURE_DISK_STORAGE));
cachedPermsList.add(new PermissionSubject(getParameters().getStorageDomainId(),
VdcObjectType.Storage, ActionGroup.CREATE_DISK));
}
return cachedPermsList;
}
private MoveOrCopyImageGroupParameters prepareChildParameters() {
MoveOrCopyImageGroupParameters parameters = new MoveOrCopyImageGroupParameters(getParameters());
if (parameters.getOperation() == ImageOperation.Copy) {
parameters.setUseCopyCollapse(true);
parameters.setAddImageDomainMapping(true);
parameters.setShouldLockImageOnRevert(false);
if (!isTemplate()) {
prepareCopyNotTemplate(parameters);
parameters.setShouldLockImageOnRevert(true);
parameters.setRevertDbOperationScope(ImageDbOperationScope.IMAGE);
}
} else {
parameters.setUseCopyCollapse(false);
}
if (parameters.getOperation() == ImageOperation.Move || isTemplate()) {
parameters.setDestinationImageId(getImageId());
parameters.setImageGroupID(getImageGroupId());
parameters.setDestImageGroupId(getImageGroupId());
}
parameters.setVolumeFormat(getDiskImage().getVolumeFormat());
parameters.setVolumeType(getDiskImage().getVolumeType());
if (isTemplate()) {
parameters.setCopyVolumeType(CopyVolumeType.SharedVol);
}
else {
parameters.setCopyVolumeType(CopyVolumeType.LeafVol);
}
parameters.setParentCommand(getActionType());
parameters.setParentParameters(getParameters());
parameters.setDiskProfileId(getImage().getDiskProfileId());
parameters.setJobWeight(Job.MAX_WEIGHT);
return parameters;
}
/**
* Prepares the copy of the VM disks and floating disks
*/
private void prepareCopyNotTemplate(MoveOrCopyImageGroupParameters parameters) {
parameters.setAddImageDomainMapping(false);
Guid newImageId = Guid.newGuid();
Guid newId = Guid.newGuid();
DiskImage image = getImage();
image.setId(newId);
image.setImageId(newImageId);
image.setDiskAlias(getDiskAlias());
image.setStorageIds(new ArrayList<>());
image.getStorageIds().add(getParameters().getStorageDomainId());
image.setQuotaId(getParameters().getQuotaId());
image.setDiskProfileId(getParameters().getDiskProfileId());
image.setImageStatus(ImageStatus.LOCKED);
image.setVmSnapshotId(null);
image.setParentId(Guid.Empty);
image.setImageTemplateId(Guid.Empty);
parameters.setDestinationImageId(newImageId);
parameters.setDestImageGroupId(newId);
}
private boolean isTemplate() {
return !(getImage().getVmEntityType() == null || !getImage().getVmEntityType().isTemplateType());
}
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
if (isCopyOperation()) {
if (!Guid.Empty.equals(getVmTemplateId())) {
return Collections.singletonMap(getVmTemplateId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.TEMPLATE, getDiskIsBeingMigratedMessage()));
}
} else {
List<Pair<VM, VmDevice>> vmsForDisk = getVmsWithVmDeviceInfoForDiskId();
if (!vmsForDisk.isEmpty()) {
return vmsForDisk.stream()
.collect(Collectors.toMap(p -> p.getFirst().getId().toString(),
p -> LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, getDiskIsBeingMigratedMessage())));
}
}
return null;
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
return Collections.singletonMap(
(getImage() != null ? getImage().getId() : Guid.Empty).toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK, getDiskIsBeingMigratedMessage()));
}
private String getDiskIsBeingMigratedMessage() {
if (cachedDiskIsBeingMigratedMessage == null) {
cachedDiskIsBeingMigratedMessage = new LockMessage(EngineMessage.ACTION_TYPE_FAILED_DISK_IS_BEING_MIGRATED)
.withOptional("DiskName", getImage() != null ? getDiskAlias() : null)
.toString();
}
return cachedDiskIsBeingMigratedMessage;
}
public String getDiskAlias() {
return StringUtils.isEmpty(getParameters().getNewAlias()) ? getImage().getDiskAlias() : getParameters().getNewAlias();
}
protected boolean setAndValidateDiskProfiles() {
getImage().setDiskProfileId(getParameters().getDiskProfileId());
return validate(diskProfileHelper.setAndValidateDiskProfiles(Collections.singletonMap(getImage(),
getParameters().getStorageDomainId()), getCurrentUser()));
}
protected boolean validatePassDiscardSupportedForDestinationStorageDomain() {
if (isMoveOperation() ||
(isCopyOperation() && isTemplate())) {
MultipleDiskVmElementValidator multipleDiskVmElementValidator = createMultipleDiskVmElementValidator();
return validate(multipleDiskVmElementValidator.isPassDiscardSupportedForDestSd(
getParameters().getStorageDomainId()));
}
return true;
}
@Override
public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() {
List<QuotaConsumptionParameter> list = new ArrayList<>();
list.add(new QuotaStorageConsumptionParameter(
getDestinationQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.CONSUME,
getParameters().getStorageDomainId(),
(double)getImage().getSizeInGigabytes()));
if (isMoveOperation()) {
if (getImage().getQuotaId() != null && !Guid.Empty.equals(getImage().getQuotaId())) {
list.add(new QuotaStorageConsumptionParameter(
getImage().getQuotaId(),
null,
QuotaConsumptionParameter.QuotaAction.RELEASE,
getParameters().getSourceDomainId(),
(double)getImage().getSizeInGigabytes()));
}
}
return list;
}
private Guid getDestinationQuotaId() {
return getParameters().getQuotaId();
}
@Override
public Map<String, String> getJobMessageProperties() {
List<StorageDomain> storageDomains = storageDomainDao.getAllForStorageDomain(getParameters().getSourceDomainId());
String sourceSDName = StringUtils.EMPTY;
if (storageDomains.size() > 0) {
sourceSDName = storageDomains.get(0).getStorageName();
}
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
jobProperties.put("sourcesd", sourceSDName);
jobProperties.put("targetsd", getStorageDomainName());
jobProperties.put("diskalias", getDiskAlias());
if (isMoveOperation()) {
jobProperties.put("action", "Moving");
} else {
jobProperties.put("action", "Copying");
}
}
return jobProperties;
}
protected StorageDomainValidator createStorageDomainValidator() {
return new StorageDomainValidator(getStorageDomain());
}
protected DiskValidator createDiskValidator() {
return new DiskValidator(getImage());
}
protected MultipleDiskVmElementValidator createMultipleDiskVmElementValidator() {
return new MultipleDiskVmElementValidator(getImage(),
diskVmElementDao.getAllDiskVmElementsByDiskId(getParameters().getImageGroupID()));
}
private boolean isMoveOperation() {
return ImageOperation.Move == getParameters().getOperation();
}
private boolean isCopyOperation() {
return ImageOperation.Copy == getParameters().getOperation();
}
}