package org.ovirt.engine.core.bll.validator.storage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.Backend;
import org.ovirt.engine.core.bll.ValidationResult;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.VM;
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.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.vdscommands.GetImagesListVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.SnapshotDao;
import org.ovirt.engine.core.dao.StorageDomainStaticDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmDeviceDao;
/**
* A validator for the {@link DiskImage} class. Since most usecases require validations of multiple {@link DiskImage}s
* (e.g., all the disks belonging to a VM/template), this class works on a {@link Collection} of {@link DiskImage}s.
*
*/
public class DiskImagesValidator {
private Iterable<DiskImage> diskImages;
public DiskImagesValidator(Iterable<DiskImage> disks) {
this.diskImages = disks;
}
public DiskImagesValidator(DiskImage... disks) {
this.diskImages = Arrays.asList(disks);
}
/**
* Validates that non of the disk are {@link ImageStatus#ILLEGAL}.
*
* @return A {@link ValidationResult} with the validation information.
*/
public ValidationResult diskImagesNotIllegal() {
return diskImagesNotInStatus(ImageStatus.ILLEGAL, EngineMessage.ACTION_TYPE_FAILED_DISKS_ILLEGAL);
}
/**
* Validates that non of the disk are {@link ImageStatus#LOCKED}.
*
* @return A {@link ValidationResult} with the validation information.
*/
public ValidationResult diskImagesNotLocked() {
return diskImagesNotInStatus(ImageStatus.LOCKED, EngineMessage.ACTION_TYPE_FAILED_DISKS_LOCKED);
}
protected DiskImage getExistingDisk(Guid id) {
return getDbFacade().getDiskImageDao().get(id);
}
protected boolean isDiskExists(Guid id) {
return DbFacade.getInstance().getBaseDiskDao().exists(id);
}
/**
* Validates that non of the disks exists
*
* @return A {@link ValidationResult} with the validation information.
*/
public ValidationResult diskImagesAlreadyExist() {
List<String> existingDisksAliases = new ArrayList<>();
for (DiskImage diskImage : diskImages) {
DiskImage existingDisk = getExistingDisk(diskImage.getId());
if (existingDisk != null) {
existingDisksAliases.add(diskImage.getDiskAlias().isEmpty() ? existingDisk.getDiskAlias() : diskImage.getDiskAlias());
}
}
if (!existingDisksAliases.isEmpty()) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_IMPORT_DISKS_ALREADY_EXIST,
String.format("$diskAliases %s", StringUtils.join(existingDisksAliases, ", ")));
}
return ValidationResult.VALID;
}
/**
* Validates that the disks exists
*
* @return A {@link ValidationResult} with the validation information.
*/
public ValidationResult diskImagesNotExist() {
List<String> disksNotExistInDbIds = new ArrayList<>();
for (DiskImage diskImage : diskImages) {
if (!isDiskExists(diskImage.getId())) {
disksNotExistInDbIds.add(diskImage.getId().toString());
}
}
if (!disksNotExistInDbIds.isEmpty()) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_DISKS_NOT_EXIST,
String.format("$diskIds %s", StringUtils.join(disksNotExistInDbIds, ", ")));
}
return ValidationResult.VALID;
}
/**
* Validates that non of the disk are in the given {@link #status}.
*
* @param status
* The status to check
* @param failMessage
* The validation message to return in case of failure.
* @return A {@link ValidationResult} with the validation information. If none of the disks are in the given status,
* {@link ValidationResult#VALID} is returned. If one or more disks are in that status, a
* {@link ValidationResult} with {@link #failMessage} and the names of the disks in that status is returned.
*/
private ValidationResult diskImagesNotInStatus(ImageStatus status, EngineMessage failMessage) {
List<String> disksInStatus = new ArrayList<>();
for (DiskImage diskImage : diskImages) {
if (diskImage.getImageStatus() == status) {
disksInStatus.add(diskImage.getDiskAlias());
}
}
if (!disksInStatus.isEmpty()) {
return new ValidationResult(failMessage,
String.format("$diskAliases %s", StringUtils.join(disksInStatus, ", ")));
}
return ValidationResult.VALID;
}
public ValidationResult diskImagesSnapshotsNotAttachedToOtherVms(boolean onlyPlugged) {
LinkedList<String> pluggedDiskSnapshotInfo = new LinkedList<>();
for (DiskImage diskImage : diskImages) {
List<VmDevice> devices = getVmDeviceDao().getVmDevicesByDeviceId(diskImage.getId(), null);
for (VmDevice device : devices) {
if (device.getSnapshotId() != null && (!onlyPlugged || device.isPlugged())) {
VM vm = getVmDao().get(device.getVmId());
Snapshot snapshot = getSnapshotDao().get(device.getSnapshotId());
pluggedDiskSnapshotInfo.add(String.format("%s ,%s, %s",
diskImage.getDiskAlias(), snapshot.getDescription(), vm.getName()));
}
}
}
if (!pluggedDiskSnapshotInfo.isEmpty()) {
EngineMessage message =
onlyPlugged ? EngineMessage.ACTION_TYPE_FAILED_VM_DISK_SNAPSHOT_IS_PLUGGED_TO_ANOTHER_VM
: EngineMessage.ACTION_TYPE_FAILED_VM_DISK_SNAPSHOT_IS_ATTACHED_TO_ANOTHER_VM;
return new ValidationResult(message,
String.format("$disksInfo %s", String.format(StringUtils.join(pluggedDiskSnapshotInfo, "%n"))));
}
return ValidationResult.VALID;
}
/**
* checks that the given disks has no derived disks on the given storage domain.
* if the provided storage domain id is null, it will be checked that there are no
* derived disks on any storage domain.
*/
public ValidationResult diskImagesHaveNoDerivedDisks(Guid storageDomainId) {
List<String> disksInfo = new LinkedList<>();
for (DiskImage diskImage : diskImages) {
if (diskImage.getVmEntityType() != null && diskImage.getVmEntityType().isTemplateType()) {
List<DiskImage> basedDisks = getDiskImageDao().getAllSnapshotsForParent(diskImage.getImageId());
for (DiskImage basedDisk : basedDisks) {
if (storageDomainId == null || basedDisk.getStorageIds().contains(storageDomainId)) {
disksInfo.add(String.format("%s (%s) ", basedDisk.getDiskAlias(), basedDisk.getId()));
}
}
}
}
if (!disksInfo.isEmpty()) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_DETECTED_DERIVED_DISKS,
String.format("$disksInfo %s",
String.format(StringUtils.join(disksInfo, "%n"))));
}
return ValidationResult.VALID;
}
/**
* Validate that the given disks has not exceeded the maximum number of images in one chain.
*/
public ValidationResult diskImagesHaveNotExceededMaxNumberOfVolumesInImageChain() {
List<String> disksInfo = new LinkedList<>();
for (DiskImage diskImage : diskImages) {
Integer numberOfVolumesInChain = getDiskImageDao().getAllSnapshotsForImageGroup(diskImage.getId()).size();
if (numberOfVolumesInChain >= getMaxVolumeChain()) {
disksInfo.add(String.format("%s (%s/%s)",
diskImage.getDiskAlias(),
numberOfVolumesInChain,
getMaxVolumeChain()));
}
}
if (!disksInfo.isEmpty()) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_MAXIMUM_LIMIT_OF_VOLUMES_IN_CHAIN,
String.format("$disksInfo %s", String.format(StringUtils.join(disksInfo, "%n"))));
}
return ValidationResult.VALID;
}
/**
* checks that the given disks do not exist on the target storage domains
* @param imageToDestinationDomainMap map containing the destination domain for each of the disks
* @param storagePoolId the storage pool ID to check whether the disks are residing on
* @return validation result indicating whether the disks don't exist on the target storage domains
*/
public ValidationResult diskImagesOnStorage(Map<Guid, Guid> imageToDestinationDomainMap, Guid storagePoolId) {
Map<Guid, List<Guid>> domainImages = new HashMap<>();
for (DiskImage diskImage : diskImages) {
Guid targetStorageDomainId = imageToDestinationDomainMap.get(diskImage.getId());
List<Guid> imagesOnStorageDomain = domainImages.get(targetStorageDomainId);
if (imagesOnStorageDomain == null) {
VDSReturnValue returnValue = Backend.getInstance().getResourceManager().runVdsCommand(
VDSCommandType.GetImagesList,
new GetImagesListVDSCommandParameters(targetStorageDomainId, storagePoolId)
);
if (returnValue.getSucceeded()) {
imagesOnStorageDomain = (List<Guid>) returnValue.getReturnValue();
domainImages.put(targetStorageDomainId, imagesOnStorageDomain);
} else {
return new ValidationResult(EngineMessage.ERROR_GET_IMAGE_LIST,
String.format("$sdName %1$s", getStorageDomainStaticDao().get(targetStorageDomainId).getName()));
}
}
if (imagesOnStorageDomain.contains(diskImage.getId())) {
return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_ALREADY_CONTAINS_DISK);
}
}
return ValidationResult.VALID;
}
/**
* Checks that each of the disks has at least one domain in valid status in the given map
* @param validDomainsForDisk Map containing valid domains for each disk
* @param storageDomains Map containing the storage domain objects
* @param message Validation message to use in case of error
* @param applicableStatuses Applicable domain statuses to use as replacement in the given message
* @return A {@link ValidationResult} with the validation information.
*/
public ValidationResult diskImagesOnAnyApplicableDomains(Map<Guid, Set<Guid>> validDomainsForDisk,
Map<Guid, StorageDomain> storageDomains,
EngineMessage message,
Set<StorageDomainStatus> applicableStatuses) {
StringBuilder disksInfo = new StringBuilder();
for (DiskImage diskImage : diskImages) {
Set<Guid> applicableDomains = validDomainsForDisk.get(diskImage.getId());
if (!applicableDomains.isEmpty()) {
continue;
}
List<String> nonApplicableStorageInfo = new LinkedList<>();
for (Guid id : diskImage.getStorageIds()) {
StorageDomain domain = storageDomains.get(id);
nonApplicableStorageInfo.add(String.format("%s - %s", domain.getName(), domain.getStatus()
.toString()));
}
disksInfo.append(String.format("%s (%s) %n",
diskImage.getDiskAlias(),
StringUtils.join(nonApplicableStorageInfo, " / ")));
}
ValidationResult result = ValidationResult.VALID;
if (disksInfo.length() > 0) {
result = new ValidationResult(message,
String.format("$disksInfo %s",
disksInfo.toString()),
String.format("$applicableStatus %s", StringUtils.join(applicableStatuses, ",")));
}
return result;
}
public ValidationResult disksInStatus(ImageStatus applicableStatus, EngineMessage message) {
for (DiskImage diskImage : diskImages) {
if (diskImage.getImageStatus() != applicableStatus) {
return new ValidationResult(message,
String.format("$status %s", applicableStatus.name()));
}
}
return ValidationResult.VALID;
}
private DbFacade getDbFacade() {
return DbFacade.getInstance();
}
protected VmDeviceDao getVmDeviceDao() {
return getDbFacade().getVmDeviceDao();
}
protected VmDao getVmDao() {
return getDbFacade().getVmDao();
}
protected SnapshotDao getSnapshotDao() {
return getDbFacade().getSnapshotDao();
}
protected DiskImageDao getDiskImageDao() {
return getDbFacade().getDiskImageDao();
}
protected StorageDomainStaticDao getStorageDomainStaticDao() {
return getDbFacade().getStorageDomainStaticDao();
}
protected int getMaxVolumeChain() {
return Config.getValue(ConfigValues.MaxImagesInChain);
}
}