package org.ovirt.engine.core.bll.storage.disk.image;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.context.CompensationContext;
import org.ovirt.engine.core.bll.storage.connection.StorageHelperDirector;
import org.ovirt.engine.core.bll.storage.utils.VdsCommandsHelper;
import org.ovirt.engine.core.bll.utils.ClusterUtils;
import org.ovirt.engine.core.bll.utils.VmDeviceUtils;
import org.ovirt.engine.core.bll.validator.storage.StorageDomainValidator;
import org.ovirt.engine.core.common.businessentities.Snapshot;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatic;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.storage.BaseDisk;
import org.ovirt.engine.core.common.businessentities.storage.CinderDisk;
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.DiskImageBase;
import org.ovirt.engine.core.common.businessentities.storage.DiskImageDynamic;
import org.ovirt.engine.core.common.businessentities.storage.DiskLunMapId;
import org.ovirt.engine.core.common.businessentities.storage.DiskStorageType;
import org.ovirt.engine.core.common.businessentities.storage.Image;
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.LUNs;
import org.ovirt.engine.core.common.businessentities.storage.LunDisk;
import org.ovirt.engine.core.common.businessentities.storage.QemuImageInfo;
import org.ovirt.engine.core.common.businessentities.storage.StorageType;
import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat;
import org.ovirt.engine.core.common.businessentities.storage.VolumeType;
import org.ovirt.engine.core.common.constants.StorageConstants;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.vdscommands.GetImageInfoVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.GetVolumeInfoVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.ImageActionsVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.PrepareImageVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.DiskVmElementDao;
import org.ovirt.engine.core.di.Injector;
import org.ovirt.engine.core.utils.collections.MultiValueMapUtils;
import org.ovirt.engine.core.utils.ovf.OvfManager;
import org.ovirt.engine.core.utils.ovf.OvfReaderException;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ImagesHandler {
public static final String DISK = "_Disk";
public static final String DefaultDriveName = "1";
private static final Logger log = LoggerFactory.getLogger(ImagesHandler.class);
/**
* The following method will find all images and storages where they located for provide template and will fill an
* diskInfoDestinationMap by imageId mapping on active storage id where image is located. The second map is
* mapping of founded storage ids to storage object
*/
public static void fillImagesMapBasedOnTemplate(VmTemplate template,
Map<Guid, DiskImage> diskInfoDestinationMap,
Map<Guid, StorageDomain> destStorages) {
List<StorageDomain> domains =
DbFacade.getInstance()
.getStorageDomainDao()
.getAllForStoragePool(template.getStoragePoolId());
fillImagesMapBasedOnTemplate(template, domains, diskInfoDestinationMap, destStorages);
}
public static void fillImagesMapBasedOnTemplate(VmTemplate template,
List<StorageDomain> domains,
Map<Guid, DiskImage> diskInfoDestinationMap,
Map<Guid, StorageDomain> destStorages) {
Map<Guid, StorageDomain> storageDomainsMap = new HashMap<>();
for (StorageDomain storageDomain : domains) {
StorageDomainValidator validator = new StorageDomainValidator(storageDomain);
if (validator.isDomainExistAndActive().isValid() && validator.domainIsValidDestination().isValid()) {
storageDomainsMap.put(storageDomain.getId(), storageDomain);
}
}
for (DiskImage image : template.getDiskTemplateMap().values()) {
for (Guid storageId : image.getStorageIds()) {
if (storageDomainsMap.containsKey(storageId)) {
ArrayList<Guid> storageIds = new ArrayList<>();
storageIds.add(storageId);
image.setStorageIds(storageIds);
diskInfoDestinationMap.put(image.getId(), image);
break;
}
}
}
if (destStorages != null) {
for (DiskImage diskImage : diskInfoDestinationMap.values()) {
Guid storageDomainId = diskImage.getStorageIds().get(0);
destStorages.put(storageDomainId, storageDomainsMap.get(storageDomainId));
}
}
}
public static boolean setDiskAlias(BaseDisk disk, VM vm) {
return setDiskAlias(disk, vm, nullSafeGetCount(vm));
}
public static boolean setDiskAlias(BaseDisk disk, VM vm, int count) {
if (disk == null) {
log.error("Disk object is null");
return false;
}
String vmName = nullSafeGetVmName(vm);
disk.setDiskAlias(getSuggestedDiskAlias(disk, vmName, count));
return true;
}
private static String nullSafeGetVmName(VM vm) {
return vm == null ? "" : vm.getName();
}
private static int nullSafeGetCount(VM vm) {
return vm == null ? 1 : vm.getDiskMapCount() + 1;
}
/**
* Suggests an alias for a disk.
* If the disk does not already have an alias, one will be generated for it.
* The generated alias will be formed as prefix_DiskXXX, where XXX is an ordinal.
*
* @param disk
* - The disk that (possibly) requires a new alias
* @param diskPrefix
* - The prefix for the newly generated alias
* @param count
* - The ordinal of disk to create an alias for (first, second, etc.).
* @return The suggested alias
*/
public static String getSuggestedDiskAlias(BaseDisk disk, String diskPrefix, int count) {
String diskAlias;
if (disk == null) {
diskAlias = getDefaultDiskAlias(diskPrefix, DefaultDriveName);
log.warn("Disk object is null, the suggested default disk alias to be used is '{}'",
diskAlias);
} else {
String defaultAlias = getDefaultDiskAlias(diskPrefix, String.valueOf(count));
diskAlias = getDiskAliasWithDefault(disk, defaultAlias);
}
return diskAlias;
}
/**
* Returns an alias for the given disk. If the disk already has an alias, it is returned. If not,
* {@code aliasIfNull} is returned.
*
* @param disk
* The disk
* @param aliasIfNull
* The alias to return if the disk does not have an alias
* @return The alias in question
*/
public static String getDiskAliasWithDefault(BaseDisk disk, String aliasIfNull) {
String diskAlias = disk.getDiskAlias();
if (StringUtils.isEmpty(diskAlias)) {
log.info("Disk alias retrieved from the client is null or empty, the suggested default disk alias to be"
+ " used is '{}'",
aliasIfNull);
return aliasIfNull;
}
return diskAlias;
}
public static String getDefaultDiskAlias(String prefix, String suffix) {
return prefix + DISK + suffix;
}
public static Map<Guid, List<DiskImage>> buildStorageToDiskMap(Collection<DiskImage> images,
Map<Guid, DiskImage> diskInfoDestinationMap) {
Map<Guid, List<DiskImage>> storageToDisksMap = new HashMap<>();
for (DiskImage disk : images) {
DiskImage diskImage = diskInfoDestinationMap.get(disk.getId());
Guid storageDomainId = diskImage.getStorageIds().get(0);
List<DiskImage> diskList = storageToDisksMap.get(storageDomainId);
if (diskList == null) {
diskList = new ArrayList<>();
storageToDisksMap.put(storageDomainId, diskList);
}
diskList.add(disk);
}
return storageToDisksMap;
}
/**
* Adds a disk image (Adds image, disk and relevant entities)
*
* @param image
* DiskImage to add
* @param active
* true if the image should be added as active
* @param imageStorageDomainMap
* storage domain map entry to map between the image and its storage domain
*/
public static void addDiskImage(DiskImage image, boolean active, ImageStorageDomainMap imageStorageDomainMap, Guid vmId) {
try {
addImage(image, active, imageStorageDomainMap);
addDiskToVmIfNotExists(image, vmId);
} catch (RuntimeException ex) {
log.error("Failed adding new disk image and related entities to db: {}", ex.getMessage());
log.debug("Exception", ex);
throw new EngineException(EngineError.DB, ex);
}
}
/**
* Gets a map of DiskImage IDs to DiskImage objects
*
* @param diskImages
* collection of DiskImage objects to create the map for
* @return map object is the collection is not null
*/
public static Map<Guid, DiskImage> getDiskImagesByIdMap(Collection<DiskImage> diskImages) {
Map<Guid, DiskImage> result = new HashMap<>();
if (diskImages != null) {
for (DiskImage diskImage : diskImages) {
result.put(diskImage.getImageId(), diskImage);
}
}
return result;
}
/**
* Calculates the max size of required for cloned DiskImages with collapse.
* The space should be calculated according to the volumes type and format.
*
* | File Domain | Block Domain
* -----|-----------------------------------------|-------------
* qcow | preallocated : disk capacity | min(used ,capacity)
* | sparse: min(used ,capacity) |
* -----|-----------------------------------------|-------------
* raw | preallocated: disk capacity | disk capacity
* | sparse: min(used,capacity) |
*
* */
public static double getTotalActualSizeOfDisk(DiskImage diskImage, StorageDomainStatic storageDomain) {
double sizeForDisk = diskImage.getSize();
if ((storageDomain.getStorageType().isFileDomain() && diskImage.getVolumeType() == VolumeType.Sparse) ||
storageDomain.getStorageType().isBlockDomain() && diskImage.getVolumeFormat() == VolumeFormat.COW) {
double usedSpace = diskImage.getActualDiskWithSnapshotsSizeInBytes();
sizeForDisk = Math.min(diskImage.getSize(), usedSpace);
}
return sizeForDisk;
}
public static boolean isImageInitialSizeSupported(StorageType storageType) {
return storageType.isBlockDomain();
}
/**
* Returns a list of image IDs for the specified DiskImages collection.
*
* @param diskImages collection of DiskImages
* @return list of image IDs ordered by the order of the retrieved list.
*/
public static List<Guid> getDiskImageIds(List<DiskImage> diskImages) {
List<Guid> result = new ArrayList<>();
if (diskImages != null) {
for (DiskImage diskImage : diskImages) {
result.add(diskImage.getImageId());
}
}
return result;
}
/**
* Adds a disk image (Adds image, disk, and relevant entities , but not VmDevice) This may be useful for Clone VMs,
* where besides adding images it is required to copy all vm devices (VmDeviceUtils.copyVmDevices) from the source
* VM
*
* @param image
* image to add
* @param active
* true if to add as active image
* @param imageStorageDomainMap
* entry of image storagte domain map
*/
public static void addDiskImageWithNoVmDevice(DiskImage image,
boolean active,
ImageStorageDomainMap imageStorageDomainMap) {
try {
addImage(image, active, imageStorageDomainMap);
addDisk(image);
} catch (RuntimeException ex) {
log.error("Failed adding new disk image and related entities to db: {}", ex.getMessage());
log.debug("Exception", ex);
throw new EngineException(EngineError.DB, ex);
}
}
/**
* Adds a disk image (Adds image, disk, and relevant entities , but not VmDevice) This may be useful for Clone VMs,
* where besides adding images it is required to copy all vm devices (VmDeviceUtils.copyVmDevices) from the source
* VM.
*/
public static void addDiskImageWithNoVmDevice(DiskImage image) {
addDiskImageWithNoVmDevice(image,
image.getActive(),
new ImageStorageDomainMap(image.getImageId(),
image.getStorageIds().get(0),
image.getQuotaId(),
image.getDiskProfileId()));
}
/**
* Adds disk to a VM without creating a VmDevice entry
*
* @param disk
* disk to add
*/
public static void addDisk(BaseDisk disk) {
if (!DbFacade.getInstance().getBaseDiskDao().exists(disk.getId())) {
DbFacade.getInstance().getBaseDiskDao().save(disk);
}
}
/**
* Adds a disk image (Adds image with active flag according to the value in image, using the first storage domain in
* the storage id as entry to the storage domain map)
*
* @param image
* DiskImage to add
*/
public static void addDiskImage(DiskImage image, Guid vmId) {
addDiskImage(image, image.getActive(), new ImageStorageDomainMap(image.getImageId(), image.getStorageIds()
.get(0), image.getQuotaId(), image.getDiskProfileId()), vmId);
}
/**
* Add image and related entities to DB (Adds image, disk image dynamic and image storage domain map)
*
* @param image
* the image to add
* @param active
* if true the image will be active
* @param imageStorageDomainMap
* entry of mapping between the storage domain and the image
*/
public static void addImage(DiskImage image, boolean active, ImageStorageDomainMap imageStorageDomainMap) {
image.setActive(active);
DbFacade.getInstance().getImageDao().save(image.getImage());
DiskImageDynamic diskDynamic = new DiskImageDynamic();
diskDynamic.setId(image.getImageId());
diskDynamic.setActualSize(image.getActualSizeInBytes());
DbFacade.getInstance().getDiskImageDynamicDao().save(diskDynamic);
if (imageStorageDomainMap != null) {
DbFacade.getInstance().getImageStorageDomainMapDao().save(imageStorageDomainMap);
}
}
/**
* Add disk if it does not exist to a given vm
*
* @param disk
* the disk to add
* @param vmId
* the ID of the vm to add to if the disk does not exist for this VM
*/
public static void addDiskToVmIfNotExists(BaseDisk disk, Guid vmId) {
if (!DbFacade.getInstance().getBaseDiskDao().exists(disk.getId())) {
addDiskToVm(disk, vmId);
}
}
/**
* Adds disk to vm
*
* @param disk
* the disk to add
* @param vmId
* the ID of the VM to add to
*/
public static void addDiskToVm(BaseDisk disk, Guid vmId) {
DbFacade.getInstance().getBaseDiskDao().save(disk);
if (disk.getDiskVmElementForVm(vmId) != null) {
getDiskVmElementDao().save(disk.getDiskVmElementForVm(vmId));
}
final VmDeviceUtils vmDeviceUtils = Injector.get(VmDeviceUtils.class);
vmDeviceUtils.addDiskDevice(vmId, disk.getId());
}
/**
* The following method unify saving of image, it will be also saved with its storage
* mapping.
*/
public static ImageStorageDomainMap saveImage(DiskImage diskImage) {
DbFacade.getInstance().getImageDao().save(diskImage.getImage());
ImageStorageDomainMap imageStorageDomainMap = new ImageStorageDomainMap(diskImage.getImageId(),
diskImage.getStorageIds()
.get(0), diskImage.getQuotaId(), diskImage.getDiskProfileId());
DbFacade.getInstance()
.getImageStorageDomainMapDao()
.save(imageStorageDomainMap);
return imageStorageDomainMap;
}
public static boolean isImagesExists(Iterable<DiskImage> images, Guid storagePoolId) {
for (DiskImage image : images) {
DiskImage fromIrs = isImageExist(storagePoolId, image);
if (fromIrs == null) {
return false;
}
}
return true;
}
private static DiskImage isImageExist(Guid storagePoolId, DiskImage image) {
DiskImage fromIrs = null;
Guid storageDomainId = image.getStorageIds().get(0);
Guid imageGroupId = image.getId() != null ? image.getId() : Guid.Empty;
try {
fromIrs = (DiskImage) Backend
.getInstance()
.getResourceManager()
.runVdsCommand(
VDSCommandType.GetImageInfo,
new GetImageInfoVDSCommandParameters(storagePoolId, storageDomainId, imageGroupId,
image.getImageId())).getReturnValue();
} catch (Exception e) {
log.debug("Unable to get image info from storage", e);
}
return fromIrs;
}
public static boolean checkImageConfiguration(StorageDomainStatic storageDomain,
DiskImageBase diskInfo, List<String> messages) {
if (!checkImageConfiguration(storageDomain, diskInfo.getVolumeType(), diskInfo.getVolumeFormat())) {
// not supported
messages.add(EngineMessage.ACTION_TYPE_FAILED_DISK_CONFIGURATION_NOT_SUPPORTED.toString());
messages.add(String.format("$%1$s %2$s", "volumeFormat", diskInfo.getVolumeFormat()));
messages.add(String.format("$%1$s %2$s", "volumeType", diskInfo.getVolumeType()));
return false;
}
return true;
}
public static boolean checkImageConfiguration(StorageDomainStatic storageDomain, VolumeType volumeType, VolumeFormat volumeFormat) {
return !((volumeType == VolumeType.Preallocated && volumeFormat == VolumeFormat.COW)
|| (storageDomain.getStorageType().isBlockDomain() && volumeType == VolumeType.Sparse && volumeFormat == VolumeFormat.RAW)
|| volumeFormat == VolumeFormat.Unassigned
|| volumeType == VolumeType.Unassigned);
}
public static boolean checkImagesConfiguration(Guid storageDomainId,
Collection<? extends Disk> disksConfigList,
List<String> messages) {
boolean result = true;
StorageDomainStatic storageDomain = DbFacade.getInstance().getStorageDomainStaticDao().get(storageDomainId);
for (Disk diskInfo : disksConfigList) {
if (DiskStorageType.IMAGE == diskInfo.getDiskStorageType()) {
result = checkImageConfiguration(storageDomain, (DiskImage) diskInfo, messages);
}
if (!result) {
break;
}
}
return result;
}
public static Map<Guid, Set<Guid>> findDomainsInApplicableStatusForDisks(Iterable<DiskImage> diskImages,
Map<Guid, StorageDomain> storageDomains,
Set<StorageDomainStatus> applicableStatuses) {
Map<Guid, Set<Guid>> disksApplicableDomainsMap = new HashMap<>();
for (DiskImage diskImage : diskImages) {
Set<Guid> diskApplicableDomain = new HashSet<>();
for (Guid storageDomainID : diskImage.getStorageIds()) {
StorageDomain domain = storageDomains.get(storageDomainID);
if (applicableStatuses.contains(domain.getStatus())) {
diskApplicableDomain.add(domain.getId());
}
}
disksApplicableDomainsMap.put(diskImage.getId(), diskApplicableDomain);
}
return disksApplicableDomainsMap;
}
/**
* @param images The images to get the storage domain IDs for
* @return A unique {@link Set} of all the storage domain IDs relevant to all the given images
*/
public static Set<Guid> getAllStorageIdsForImageIds(Collection<DiskImage> images) {
Set<Guid> domainsIds = new HashSet<>();
for (DiskImage image : images) {
domainsIds.addAll(image.getStorageIds());
}
return domainsIds;
}
public static void fillImagesBySnapshots(VM vm) {
for (Disk disk : vm.getDiskMap().values()) {
if (disk.getDiskStorageType().isInternal()) {
DiskImage diskImage = (DiskImage) disk;
diskImage.getSnapshots().addAll(DbFacade.getInstance()
.getDiskImageDao()
.getAllSnapshotsForLeaf(diskImage.getImageId()));
}
}
}
public static void removeDiskImage(DiskImage diskImage, Guid vmId) {
try {
removeDiskFromVm(vmId, diskImage.getId());
removeImage(diskImage);
} catch (RuntimeException ex) {
log.error("Failed to remove disk image and related entities from db: {}", ex.getMessage());
log.debug("Exception", ex);
throw new EngineException(EngineError.DB, ex);
}
}
public static void removeLunDisk(LunDisk lunDisk) {
DbFacade.getInstance()
.getVmDeviceDao()
.remove(new VmDeviceId(lunDisk.getId(),
null));
LUNs lun = lunDisk.getLun();
DbFacade.getInstance()
.getDiskLunMapDao()
.remove(new DiskLunMapId(lunDisk.getId(), lun.getLUNId()));
DbFacade.getInstance().getBaseDiskDao().remove(lunDisk.getId());
lun.setLunConnections(new ArrayList<>(DbFacade.getInstance()
.getStorageServerConnectionDao()
.getAllForLun(lun.getLUNId())));
if (!lun.getLunConnections().isEmpty()) {
StorageHelperDirector.getInstance().getItem(
lun.getLunConnections().get(0).getStorageType()).removeLun(lun);
} else {
// if there are no connections then the lun is fcp.
StorageHelperDirector.getInstance().getItem(StorageType.FCP).removeLun(lun);
}
}
// the last image in each list is the leaf
public static Map<Guid, List<DiskImage>> getImagesLeaf(List<DiskImage> images) {
Map<Guid, List<DiskImage>> retVal = new HashMap<>();
for (DiskImage image : images) {
MultiValueMapUtils.addToMap(image.getId(), image, retVal);
}
for (List<DiskImage> list : retVal.values()) {
sortImageList(list);
}
return retVal;
}
public static void sortImageList(List<DiskImage> images) {
List<DiskImage> hold = new ArrayList<>();
DiskImage curr = null;
// find the first image
for (int i = 0; i < images.size(); i++) {
int pos = getFirstImage(images, images.get(i));
if (pos == -1) {
curr = images.get(i);
hold.add(images.get(i));
images.remove(images.get(i));
break;
}
}
while (images.size() > 0) {
int pos = getNextImage(images, curr);
if (pos == -1) {
log.error("Image list error in SortImageList");
break;
}
curr = images.get(pos);
hold.add(images.get(pos));
images.remove(images.get(pos));
}
for (DiskImage image : hold) {
images.add(image);
}
}
// function return the index of image that is its child
private static int getNextImage(List<DiskImage> images, DiskImage curr) {
for (int i = 0; i < images.size(); i++) {
if (images.get(i).getParentId().equals(curr.getImageId())) {
return i;
}
}
return -1;
}
// function return the index of the image that has no parent
private static int getFirstImage(List<DiskImage> images, DiskImage curr) {
for (int i = 0; i < images.size(); i++) {
if (curr.getParentId().equals(images.get(i).getImageId())) {
return i;
}
}
return -1;
}
public static void removeImage(DiskImage diskImage) {
DbFacade.getInstance()
.getImageStorageDomainMapDao()
.remove(diskImage.getImageId());
DbFacade.getInstance().getDiskImageDynamicDao().remove(diskImage.getImageId());
DbFacade.getInstance().getImageDao().remove(diskImage.getImageId());
}
public static void removeDiskFromVm(Guid vmGuid, Guid diskId) {
DbFacade.getInstance().getVmDeviceDao().remove(new VmDeviceId(diskId, vmGuid));
DbFacade.getInstance().getBaseDiskDao().remove(diskId);
}
public static void updateImageStatus(Guid imageId, ImageStatus imageStatus) {
DbFacade.getInstance().getImageDao().updateStatus(imageId, imageStatus);
}
public static void updateAllDiskImageSnapshotsStatusWithCompensation(final Guid diskId,
final ImageStatus status,
ImageStatus statusForCompensation,
final CompensationContext compensationContext) {
updateAllDiskImagesSnapshotsStatusInTransactionWithCompensation(Collections.singletonList(diskId), status, statusForCompensation, compensationContext);
}
public static DiskImage getSnapshotLeaf(Guid diskId) {
List<DiskImage> diskSnapshots =
DbFacade.getInstance().getDiskImageDao().getAllSnapshotsForImageGroup(diskId);
sortImageList(diskSnapshots);
return diskSnapshots.get(diskSnapshots.size() - 1);
}
public static List<DiskImage> getCinderLeafImages(List<Disk> disks) {
List<DiskImage> leafCinderDisks = new ArrayList<>();
List<CinderDisk> cinderDisks = DisksFilter.filterCinderDisks(disks);
for (CinderDisk cinder : cinderDisks) {
leafCinderDisks.add(getSnapshotLeaf(cinder.getId()));
}
return leafCinderDisks;
}
public static void updateAllDiskImagesSnapshotsStatusInTransactionWithCompensation(final Collection<Guid> diskIds,
final ImageStatus status,
ImageStatus statusForCompensation,
final CompensationContext compensationContext) {
if (compensationContext != null) {
for (Guid diskId : diskIds) {
List<DiskImage> diskSnapshots =
DbFacade.getInstance().getDiskImageDao().getAllSnapshotsForImageGroup(diskId);
for (DiskImage diskSnapshot : diskSnapshots) {
diskSnapshot.setImageStatus(statusForCompensation);
compensationContext.snapshotEntityStatus(diskSnapshot.getImage());
}
}
TransactionSupport.executeInScope(TransactionScopeOption.Required, () -> {
for (Guid diskId : diskIds) {
DbFacade.getInstance().getImageDao().updateStatusOfImagesByImageGroupId(diskId, status);
}
compensationContext.stateChanged();
return null;
});
} else {
TransactionSupport.executeInScope(TransactionScopeOption.Required, () -> {
for (Guid diskId : diskIds) {
DbFacade.getInstance().getImageDao().updateStatusOfImagesByImageGroupId(diskId, status);
}
return null;
});
}
}
private static DiskImage getDiskImageById(Guid id, Iterable<DiskImage> diskImages) {
for (DiskImage diskImage : diskImages) {
if (diskImage.getId().equals(id)) {
return diskImage;
}
}
return null;
}
/**
* Returns the subtraction set of the specified image lists (based on images' IDs)
* @param images full list
* @param imagesToSubtract images to subtract list
* @return the subtraction set
*/
public static List<DiskImage> imagesSubtract(Iterable<DiskImage> images, Iterable<DiskImage> imagesToSubtract) {
List<DiskImage> subtract = new ArrayList<>();
for (DiskImage image : images) {
if (getDiskImageById(image.getId(), imagesToSubtract) == null) {
subtract.add(image);
}
}
return subtract;
}
/**
* Returns the intersection set of the specified image lists (based on images' IDs)
* @param images1 1st list
* @param images2 2nd list
* @return the intersection set
*/
public static List<DiskImage> imagesIntersection(Iterable<DiskImage> images1, Iterable<DiskImage> images2) {
List<DiskImage> intersection = new ArrayList<>();
for (DiskImage image : images1) {
if (getDiskImageById(image.getId(), images2) != null) {
intersection.add(image);
}
}
return intersection;
}
/**
* Prepare a single {@link org.ovirt.engine.core.common.businessentities.Snapshot} object representing a snapshot of a given VM without the given disk.
*/
public static Snapshot prepareSnapshotConfigWithoutImageSingleImage(Snapshot snapshot, Guid imageId, OvfManager ovfManager) {
return prepareSnapshotConfigWithAlternateImage(snapshot, imageId, null, ovfManager);
}
/**
* Prepare a single {@link org.ovirt.engine.core.common.businessentities.Snapshot} object representing a snapshot of a given VM without the given disk,
* substituting a new disk in its place if a new disk is provided to the method.
*/
public static Snapshot prepareSnapshotConfigWithAlternateImage(Snapshot snapshot, Guid oldImageId, DiskImage newImage, OvfManager ovfManager) {
try {
String snapConfig = snapshot.getVmConfiguration();
if (snapshot.isVmConfigurationAvailable() && snapConfig != null) {
VM vmSnapshot = new VM();
ArrayList<DiskImage> snapshotImages = new ArrayList<>();
ovfManager.importVm(snapConfig, vmSnapshot, snapshotImages, new ArrayList<>());
// Remove the image from the disk list
Iterator<DiskImage> diskIter = snapshotImages.iterator();
while (diskIter.hasNext()) {
DiskImage imageInList = diskIter.next();
if (imageInList.getImageId().equals(oldImageId)) {
log.debug("Recreating vmSnapshot '{}' without the image '{}'", snapshot.getId(), oldImageId);
diskIter.remove();
break;
}
}
if (newImage != null) {
log.debug("Adding image '{}' to vmSnapshot '{}'", newImage.getImageId(), snapshot.getId());
newImage.setDiskVmElements(Collections.singletonList(getDiskVmElementDao().get(new VmDeviceId(newImage.getId(), vmSnapshot.getId()))));
snapshotImages.add(newImage);
}
String newOvf = ovfManager.exportVm(vmSnapshot, snapshotImages, ClusterUtils.getCompatibilityVersion(vmSnapshot));
snapshot.setVmConfiguration(newOvf);
}
} catch (OvfReaderException e) {
log.error("Can't remove image '{}' from snapshot '{}'", oldImageId, snapshot.getId());
}
return snapshot;
}
public static DiskImage createDiskImageWithExcessData(DiskImage diskImage, Guid sdId) {
DiskImage dummy = DiskImage.copyOf(diskImage);
dummy.setStorageIds(new ArrayList<>(Collections.singletonList(sdId)));
dummy.getSnapshots().addAll(DbFacade.getInstance().getDiskImageDao().getAllSnapshotsForLeaf(dummy.getImageId()));
return dummy;
}
/**
* This method is used for storage allocation validations, where the disks are the template's,
* which could have another volume type/format than the target disk volume type/format, which is not yet created.
* "Real" override for these values is done in CreateSnapshotCommand, when creating the new DiskImages.
*/
public static List<DiskImage> getDisksDummiesForStorageAllocations(Collection<DiskImage> originalDisks) {
List<DiskImage> diskDummies = new ArrayList<>(originalDisks.size());
for (DiskImage diskImage : originalDisks) {
DiskImage clone = DiskImage.copyOf(diskImage);
clone.setVolumeType(VolumeType.Sparse);
clone.setVolumeFormat(VolumeFormat.COW);
diskDummies.add(clone);
}
return diskDummies;
}
public static List<DiskImage> getSnapshotsDummiesForStorageAllocations(Collection<DiskImage> originalDisks) {
List<DiskImage> diskDummies = new ArrayList<>();
for (DiskImage snapshot : originalDisks) {
DiskImage clone = DiskImage.copyOf(snapshot);
// Add the child snapshot into which the deleted snapshot is going to be merged to the
// DiskImage for StorageDomainValidator to handle
List<DiskImage> snapshots = DbFacade.getInstance().getDiskImageDao().getAllSnapshotsForParent(clone.getImageId());
clone.getSnapshots().clear();
clone.getSnapshots().add(clone); // Add the clone itself since snapshots should contain the entire chain.
clone.getSnapshots().addAll(snapshots);
diskDummies.add(clone);
}
return diskDummies;
}
protected static DiskImage getVolumeInfoFromVdsm(Guid storagePoolId, Guid newStorageDomainID, Guid newImageGroupId,
Guid newImageId) {
return (DiskImage) VdsCommandsHelper.runVdsCommandWithFailover(
VDSCommandType.GetVolumeInfo,
new GetVolumeInfoVDSCommandParameters(storagePoolId, newStorageDomainID, newImageGroupId,
newImageId), storagePoolId, null).getReturnValue();
}
public static QemuImageInfo getQemuImageInfoFromVdsm(Guid storagePoolId,
Guid newStorageDomainID,
Guid newImageGroupId,
Guid newImageId,
Guid vdsId,
boolean shouldPrepareAndTeardown) {
if (vdsId == null) {
vdsId = VdsCommandsHelper.getHostForExecution(storagePoolId, Collections.emptyList());
}
QemuImageInfo qemuImageInfo = null;
if (shouldPrepareAndTeardown) {
prepareImage(storagePoolId, newStorageDomainID, newImageGroupId, newImageId, vdsId);
}
try {
qemuImageInfo = (QemuImageInfo) Backend.getInstance()
.getResourceManager()
.runVdsCommand(VDSCommandType.GetQemuImageInfo,
new GetVolumeInfoVDSCommandParameters(vdsId,
storagePoolId,
newStorageDomainID,
newImageGroupId,
newImageId)).getReturnValue();
} catch (Exception e) {
log.error("Unable to get qemu image info from storage", e);
} finally {
if (shouldPrepareAndTeardown) {
try {
teardownImage(storagePoolId, newStorageDomainID, newImageGroupId, newImageId, vdsId);
} catch (Exception e) {
log.warn("Unable to tear down image", e);
}
}
}
return qemuImageInfo;
}
public static DiskImage cloneDiskImage(Guid storageDomainId,
Guid newImageGroupId,
Guid newImageGuid,
DiskImage srcDiskImage,
Guid diskProfileId,
Guid snapshotId,
DiskImage diskImageFromClient) {
DiskImage clonedDiskImage = DiskImage.copyOf(srcDiskImage);
clonedDiskImage.setImageId(newImageGuid);
clonedDiskImage.setParentId(Guid.Empty);
clonedDiskImage.setImageTemplateId(Guid.Empty);
clonedDiskImage.setVmSnapshotId(snapshotId);
clonedDiskImage.setId(newImageGroupId);
clonedDiskImage.setLastModifiedDate(new Date());
clonedDiskImage.setVolumeFormat(srcDiskImage.getVolumeFormat());
clonedDiskImage.setVolumeType(srcDiskImage.getVolumeType());
ArrayList<Guid> storageIds = new ArrayList<>();
storageIds.add(storageDomainId);
clonedDiskImage.setStorageIds(storageIds);
clonedDiskImage.setDiskProfileId(diskProfileId);
// If volume information was changed at client , use its volume information.
// If volume information was not changed at client - use the volume information of the ancestral image
if (diskImageFromClient != null) {
if (volumeInfoChanged(diskImageFromClient, srcDiskImage)) {
changeVolumeInfo(clonedDiskImage, diskImageFromClient);
} else {
DiskImage ancestorDiskImage = getDiskImageDao().getAncestor(srcDiskImage.getImageId());
changeVolumeInfo(clonedDiskImage, ancestorDiskImage);
}
} else {
DiskImage ancestorDiskImage = getDiskImageDao().getAncestor(srcDiskImage.getImageId());
changeVolumeInfo(clonedDiskImage, ancestorDiskImage);
}
return clonedDiskImage;
}
private static DiskImageDao getDiskImageDao() {
return DbFacade.getInstance().getDiskImageDao();
}
private static DiskVmElementDao getDiskVmElementDao() {
return DbFacade.getInstance().getDiskVmElementDao();
}
private static boolean volumeInfoChanged(DiskImage diskImageFromClient, DiskImage srcDiskImage) {
return diskImageFromClient.getVolumeFormat() != srcDiskImage.getVolumeFormat() || diskImageFromClient.getVolumeType() != srcDiskImage.getVolumeType();
}
protected static void changeVolumeInfo(DiskImage clonedDiskImage, DiskImage diskImageFromClient) {
clonedDiskImage.setVolumeFormat(diskImageFromClient.getVolumeFormat());
clonedDiskImage.setVolumeType(diskImageFromClient.getVolumeType());
}
/**
* This method is use to compute the initial size of an image base on the source image size
* It is needed when copying/moving an existing image in order to create the destination image
* with the right size from the beginning, saving the process of extending the allocation.
*
* @param sourceImage The source image GUID
* @param destFormat The volume format of the destination image (COW/RAW)
* @param storagePoolId The storage pool GUID
* @param srcDomain The storage domain where the source image is located
* @param dstDomain The storage domain where the image will be copied to
* @param imageGroupID The image group GUID of the source image
* @return the computed initial size in bytes or null if it is not needed/supported
*/
public static Long determineImageInitialSize(Image sourceImage,
VolumeFormat destFormat,
Guid storagePoolId,
Guid srcDomain,
Guid dstDomain,
Guid imageGroupID) {
// We don't support Sparse-RAW volumes on block domains, therefore if the volume is RAW there is no
// need to pass initial size (it can be only preallocated).
if (isInitialSizeSupportedForFormat(destFormat, dstDomain)) {
//TODO: inspect if we can rely on the database to get the actual size.
DiskImage imageInfoFromStorage = getVolumeInfoFromVdsm(storagePoolId,
srcDomain, imageGroupID, sourceImage.getId());
return computeCowImageNeededSize(sourceImage.getVolumeFormat(), imageInfoFromStorage.getActualSizeInBytes());
}
return null;
}
private static long computeCowImageNeededSize(VolumeFormat sourceFormat, long actualSize) {
// When vdsm creates a COW volume with provided initial size the size is multiplied by 1.1 to prevent a
// case in which we won't have enough space. If the source is already COW we don't need the additional
// space.
return sourceFormat == VolumeFormat.COW
? Double.valueOf(Math.ceil(actualSize / StorageConstants.QCOW_OVERHEAD_FACTOR)).longValue()
: actualSize;
}
/**
* This method is use to compute the initial size of a disk image based on the source disk image size,
* including all the existing snapshots.
* It is needed when copying/moving an existing image with collapse in order to create the destination disk image
* with the right size from the beginning, saving the process of extending the allocation.
*
* @param sourceImage The source disk image
* @param destFormat The volume format of the destination image (COW/RAW)
* @param srcDomain The storage domain where the disk image is copied from
* @param dstDomain The storage domain where the disk image will be copied to
* @return the computed initial size in bytes or null if it is not needed/supported
*/
public static Long determineTotalImageInitialSize(DiskImage sourceImage,
VolumeFormat destFormat,
Guid srcDomain,
Guid dstDomain) {
if (isInitialSizeSupportedForFormat(destFormat, dstDomain)) {
double totalSizeForClonedDisk = getTotalActualSizeOfDisk(sourceImage,
DbFacade.getInstance().getStorageDomainDao().get(srcDomain).getStorageStaticData());
return computeCowImageNeededSize(sourceImage.getVolumeFormat(), Double.valueOf(totalSizeForClonedDisk).longValue());
}
return null;
}
private static boolean isInitialSizeSupportedForFormat(VolumeFormat destFormat, Guid dstDomain) {
return destFormat == VolumeFormat.COW &&
isImageInitialSizeSupported(
DbFacade.getInstance().getStorageDomainDao().get(dstDomain).getStorageType());
}
public static void prepareImage(Guid storagePoolId,
Guid newStorageDomainID,
Guid newImageGroupId,
Guid newImageId,
Guid vdsId) {
Backend.getInstance()
.getResourceManager()
.runVdsCommand(VDSCommandType.PrepareImage, new PrepareImageVDSCommandParameters(vdsId,
storagePoolId,
newStorageDomainID,
newImageGroupId,
newImageId, true));
}
public static void teardownImage(Guid storagePoolId,
Guid newStorageDomainID,
Guid newImageGroupId,
Guid newImageId,
Guid vdsId) {
Backend.getInstance()
.getResourceManager()
.runVdsCommand(VDSCommandType.TeardownImage,
new ImageActionsVDSCommandParameters(vdsId,
storagePoolId,
newStorageDomainID,
newImageGroupId,
newImageId));
}
}