package org.ovirt.engine.core.bll.storage.disk.image;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.CommandBase;
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.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator;
import org.ovirt.engine.core.bll.validator.storage.DiskValidator;
import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator;
import org.ovirt.engine.core.bll.validator.storage.StoragePoolValidator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.AmendImageGroupVolumesCommandParameters;
import org.ovirt.engine.core.common.action.AmendVolumeCommandParameters;
import org.ovirt.engine.core.common.action.LockProperties;
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.VdsmImageLocationInfo;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.storage.DiskImage;
import org.ovirt.engine.core.common.businessentities.storage.ImageStatus;
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.VmDao;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
@NonTransactiveCommandAttribute
public class AmendImageGroupVolumesCommand<T extends AmendImageGroupVolumesCommandParameters>
extends CommandBase<T> implements SerialChildExecutingCommand {
@Inject
private DiskDao diskDao;
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmDao vmDao;
private DiskImage diskImage;
private List<Pair<VM, VmDevice>> vmsForDisk = new ArrayList<>();
public AmendImageGroupVolumesCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
private DiskImage getDiskImage() {
if (diskImage == null) {
diskImage = (DiskImage) diskDao.get(getParameters().getImageGroupID());
}
return diskImage;
}
@Override
protected boolean validate() {
DiskValidator diskValidator = new DiskValidator(getDiskImage());
if (!validate(diskValidator.isDiskExists()) &&
!validate(diskValidator.isDiskPluggedToVmsThatAreNotDown(false, vmsForDisk))) {
return false;
}
setStoragePoolId(getDiskImage().getStoragePoolId());
if (!validate(new StoragePoolValidator(getStoragePool()).isUp())) {
return false;
}
if (!FeatureSupported.qcowCompatSupported(getStoragePool().getCompatibilityVersion())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_AMEND_NOT_SUPPORTED_BY_DC_VERSION,
String.format("$dataCenterVersion %s", getStoragePool().getCompatibilityVersion().toString()));
}
if (getDiskImage().getVmEntityType().isTemplateType()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_CANT_AMEND_TEMPLATE_DISK);
}
setStorageDomainId(getDiskImage().getStorageIds().get(0));
StorageDomainValidator storageDomainValidator = new StorageDomainValidator(getStorageDomain());
if (!validate(storageDomainValidator.isDomainExistAndActive())) {
return false;
}
DiskImagesValidator diskImagesValidator = new DiskImagesValidator(Collections.singletonList(getDiskImage()));
return validate(diskImagesValidator.diskImagesNotIllegal()) &&
validate(diskImagesValidator.diskImagesNotLocked());
}
@Override
protected void executeCommand() {
lockImageInDb();
List<DiskImage> images = diskImageDao
.getAllSnapshotsForImageGroup(getParameters().getImageGroupID());
getParameters().setImageIds(ImagesHandler.getDiskImageIds(images.stream()
.filter(disk -> disk.isQcowFormat())
.collect(Collectors.toList())));
persistCommand(getActionType(), getCallback() != null);
setSucceeded(true);
}
@Override
public Map<String, String> getJobMessageProperties() {
if (jobProperties == null) {
jobProperties = super.getJobMessageProperties();
}
jobProperties.put("action", "Amending");
jobProperties.put("diskalias", getDiskImage().getDiskAlias());
return jobProperties;
}
@Override
public CommandCallback getCallback() {
return new SerialChildCommandsExecutionCallback();
}
@Override
public boolean performNextOperation(int completedChildren) {
if (completedChildren == getParameters().getImageIds().size()) {
return false;
}
Guid imageId = getParameters().getImageIds().get(completedChildren);
log.info("Starting child command {} of {}, image '{}'",
completedChildren + 1, getParameters().getImageIds().size(), imageId);
amendVolume(imageId);
return true;
}
private void amendVolume(Guid imageId) {
AmendVolumeCommandParameters parameters =
new AmendVolumeCommandParameters(getDiskImage().getStoragePoolId(),
buildImageLocationInfo(getDiskImage().getStorageIds().get(0), getDiskImage().getId(), imageId),
getParameters().getQcowCompat());
parameters.setEndProcedure(EndProcedure.COMMAND_MANAGED);
parameters.setParentCommand(getActionType());
parameters.setParentParameters(getParameters());
parameters.setJobWeight(getParameters().getOperationsJobWeight().get(imageId.toString()));
runInternalActionWithTasksContext(VdcActionType.AmendVolume, parameters);
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
DiskImage diskImage = getDiskImage();
if (diskImage == null) {
return Collections.emptyList();
}
return Collections.singletonList(
new PermissionSubject(diskImage.getId(),
VdcObjectType.Disk,
getActionType().getActionGroup()));
}
private void lockImageInDb() {
final DiskImage diskImage = getDiskImage();
TransactionSupport.executeInNewTransaction(() -> {
getCompensationContext().snapshotEntityStatus(diskImage.getImage());
ImagesHandler.updateImageStatus(diskImage.getImageId(), ImageStatus.LOCKED);
getCompensationContext().stateChanged();
return null;
});
}
private void unlockImageInDb() {
DiskImage diskImage = getDiskImage();
ImagesHandler.updateImageStatus(diskImage.getImageId(), ImageStatus.OK);
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(LockProperties.Scope.Execution);
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
return getDiskImage() != null ? Collections.singletonMap(getDiskImage().getId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK, EngineMessage.ACTION_TYPE_FAILED_DISKS_LOCKED))
: null;
}
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
if (getDiskImage() == null) {
return null;
}
if (getDiskImage().getVmEntityType().isVmType()) {
return getSharedLocksForVmDisk();
}
log.warn("No shared locks are taken while amending disk of entity: {}", getDiskImage().getVmEntityType());
return null;
}
private Map<String, Pair<String, String>> getSharedLocksForVmDisk() {
Map<String, Pair<String, String>> lockMap = new HashMap<>();
if (getDiskImage() != null) {
List<Pair<VM, VmDevice>> vmsForDisk = vmDao.getVmsWithPlugInfo(getDiskImage().getId());
if (!vmsForDisk.isEmpty()) {
for (Pair<VM, VmDevice> pair : vmsForDisk) {
lockMap.put(pair.getFirst().getId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM,
EngineMessage.ACTION_TYPE_FAILED_VM_IS_LOCKED));
}
}
}
return lockMap;
}
@Override
protected void endSuccessfully() {
super.endSuccessfully();
unlockImageInDb();
}
@Override
protected void endWithFailure() {
super.endWithFailure();
unlockImageInDb();
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__AMEND);
addValidationMessage(EngineMessage.VAR__TYPE__DISK);
}
@Override
public AuditLogType getAuditLogTypeValue() {
addCustomValue("DiskAlias", getDiskImage().getDiskAlias());
switch (getActionState()) {
case EXECUTE:
return getSucceeded() ? AuditLogType.USER_AMEND_IMAGE_START
: AuditLogType.USER_AMEND_IMAGE_FINISH_FAILURE;
case END_SUCCESS:
return getSucceeded() ? AuditLogType.USER_AMEND_IMAGE_FINISH_SUCCESS
: AuditLogType.USER_AMEND_IMAGE_FINISH_FAILURE;
default:
return AuditLogType.USER_AMEND_IMAGE_FINISH_FAILURE;
}
}
private VdsmImageLocationInfo buildImageLocationInfo(Guid domId, Guid imageGroupId, Guid imageId) {
return new VdsmImageLocationInfo(domId, imageGroupId, imageId, null);
}
}