package org.ovirt.engine.core.bll; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_ACTIVE; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_NOT_SHAREABLE; import static org.ovirt.engine.core.bll.storage.disk.image.DisksFilter.ONLY_PLUGGED; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.network.vm.VnicProfileHelper; import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter; import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.validator.VmValidator; import org.ovirt.engine.core.bll.validator.storage.DiskImagesValidator; import org.ovirt.engine.core.bll.validator.storage.MultipleStorageDomainsValidator; 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.AddVmParameters; import org.ovirt.engine.core.common.action.MoveOrCopyImageGroupParameters; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.asynctasks.EntityInfo; 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.network.VmNetworkInterface; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.CinderDisk; import org.ovirt.engine.core.common.businessentities.storage.CopyVolumeType; 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.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.errors.EngineError; import org.ovirt.engine.core.common.errors.EngineException; 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.StorageDomainDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; /** * This abstract class holds helper methods for concrete command classes that require to add a VM and clone an image in * the process */ public abstract class AddVmAndCloneImageCommand<T extends AddVmParameters> extends AddVmCommand<T> { @Inject private StorageDomainDao storageDomainDao; @Inject private DiskDao diskDao; @Inject private DiskImageDao diskImageDao; @Inject private VmStaticDao vmStaticDao; protected AddVmAndCloneImageCommand(Guid commandId) { super(commandId); } public AddVmAndCloneImageCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected boolean validateIsImagesOnDomains() { return true; } @Override protected Map<String, Pair<String, String>> getSharedLocks() { return null; } protected void copyDiskImage( DiskImage diskImage, Guid srcStorageDomainId, Guid destStorageDomainId, Guid diskProfileId, VdcActionType parentCommandType) { DiskImage newDiskImage = ImagesHandler.cloneDiskImage(destStorageDomainId, Guid.newGuid(), Guid.newGuid(), diskImage, diskProfileId, getVmSnapshotId(), diskInfoDestinationMap != null ? diskInfoDestinationMap.get(diskImage.getId()) : null); ImagesHandler.setDiskAlias(newDiskImage, getVm()); MoveOrCopyImageGroupParameters parameters = createCopyParameters(newDiskImage, srcStorageDomainId, diskImage.getId(), diskImage.getImageId(), parentCommandType); parameters.setRevertDbOperationScope(ImageDbOperationScope.IMAGE); VdcReturnValueBase result = executeChildCopyingCommand(parameters); handleCopyResult(diskImage, newDiskImage, result); } @Override protected void removeVmRelatedEntitiesFromDb() { removeVmImages(); super.removeVmRelatedEntitiesFromDb(); } private void removeVmImages() { // Remove vm images, in case they were not already removed by child commands List<VdcActionParametersBase> imageParams = getParameters().getImagesParameters(); for (VdcActionParametersBase param : imageParams) { DiskImage diskImage = getDiskImageToRemoveByParam((MoveOrCopyImageGroupParameters) param); if (diskImage != null) { ImagesHandler.removeDiskImage(diskImage, getVmId()); } } } @Override protected Collection<DiskImage> getImagesToCheckDestinationStorageDomains() { return getDiskImagesToBeCloned(); } protected MoveOrCopyImageGroupParameters createCopyParameters(DiskImage diskImage, Guid srcStorageDomainId, Guid srcImageGroupId, Guid srcImageId, VdcActionType parentCommandType) { MoveOrCopyImageGroupParameters params = new MoveOrCopyImageGroupParameters(getVmId(), srcImageGroupId, srcImageId, diskImage.getId(), diskImage.getImageId(), diskImage.getStorageIds().get(0), ImageOperation.Copy); params.setAddImageDomainMapping(false); params.setCopyVolumeType(CopyVolumeType.LeafVol); params.setVolumeFormat(diskImage.getVolumeFormat()); params.setVolumeType(diskImage.getVolumeType()); params.setUseCopyCollapse(true); params.setSourceDomainId(srcStorageDomainId); params.setWipeAfterDelete(diskImage.isWipeAfterDelete()); params.setEntityInfo(new EntityInfo(VdcObjectType.VM, getVmId())); params.setParentParameters(getParameters()); params.setParentCommand(parentCommandType); return params; } private List<DiskImage> getDiskImagesToValidate() { List<Disk> disks = diskDao.getAllForVm(getSourceVmFromDb().getId()); List<DiskImage> allDisks = DisksFilter.filterImageDisks(disks, ONLY_NOT_SHAREABLE, ONLY_ACTIVE); List<CinderDisk> cinderDisks = DisksFilter.filterCinderDisks(disks, ONLY_PLUGGED); allDisks.addAll(cinderDisks); return allDisks; } @Override protected boolean validate() { List<DiskImage> disksToCheck = getDiskImagesToValidate(); DiskImagesValidator diskImagesValidator = new DiskImagesValidator(disksToCheck); if (!validate(diskImagesValidator.diskImagesNotLocked())) { return false; } Set<Guid> storageIds = ImagesHandler.getAllStorageIdsForImageIds(disksToCheck); MultipleStorageDomainsValidator storageValidator = new MultipleStorageDomainsValidator(getStoragePoolId(), storageIds); if (!validate(storageValidator.allDomainsExistAndActive())) { return false; } if (!validate(new VmValidator(getSourceVmFromDb()).vmNotLocked())) { return false; } // Run all checks for AddVm, now that it is determined snapshot exists if (!super.validate()) { return false; } for (DiskImage diskImage : getDiskImagesToBeCloned()) { if (diskImage.getDiskStorageType() == DiskStorageType.IMAGE && !checkImageConfiguration(diskImage)) { return false; } } return true; } protected boolean checkImageConfiguration(DiskImage diskImage) { return ImagesHandler.checkImageConfiguration(destStorages.get(diskInfoDestinationMap.get(diskImage.getId()) .getStorageIds() .get(0)) .getStorageStaticData(), diskImage, getReturnValue().getValidationMessages()); } /** * Handle the result of copying the image * @param srcDiskImage * disk image that represents the source image * @param copiedDiskImage * disk image that represents the copied image * @param result * result of execution of child command */ private void handleCopyResult(DiskImage srcDiskImage, DiskImage copiedDiskImage, VdcReturnValueBase result) { // If a copy cannot be made, abort if (!result.getSucceeded()) { throw new EngineException(EngineError.VolumeCreationError); } else { ImagesHandler.addDiskImageWithNoVmDevice(copiedDiskImage); getTaskIdList().addAll(result.getInternalVdsmTaskIdList()); getSrcDiskIdToTargetDiskIdMapping().put(srcDiskImage.getId(), copiedDiskImage.getId()); } } /** * Executes the child command responsible for the image copying * @param parameters * parameters for copy */ protected VdcReturnValueBase executeChildCopyingCommand(VdcActionParametersBase parameters) { return runInternalActionWithTasksContext(getChildActionType(), parameters); } @Override protected boolean buildAndCheckDestStorageDomains() { if (diskInfoDestinationMap.isEmpty()) { List<StorageDomain> domains = storageDomainDao.getAllForStoragePool(getStoragePoolId()); Map<Guid, StorageDomain> storageDomainsMap = new HashMap<>(); for (StorageDomain storageDomain : domains) { StorageDomainValidator validator = new StorageDomainValidator(storageDomain); if (validate(validator.isDomainExistAndActive()) && validate(validator.domainIsValidDestination())) { storageDomainsMap.put(storageDomain.getId(), storageDomain); } } for (Disk disk : getDiskImagesToBeCloned()) { DiskImage image = (DiskImage) disk; for (Guid storageId : image.getStorageIds()) { if (storageDomainsMap.containsKey(storageId)) { diskInfoDestinationMap.put(image.getId(), image); break; } } } if (getDiskImagesToBeCloned().size() != diskInfoDestinationMap.size()) { logErrorOneOrMoreActiveDomainsAreMissing(); return false; } List<Guid> storageDomainDest = new ArrayList<>(); for (DiskImage diskImage : diskInfoDestinationMap.values()) { Guid storageDomainId = diskImage.getStorageIds().get(0); if (storageDomainDest.contains(storageDomainId)) { destStorages.put(storageDomainId, storageDomainsMap.get(storageDomainId)); } storageDomainDest.add(storageDomainId); } return true; } return super.buildAndCheckDestStorageDomains(); } @Override protected boolean validateFreeSpace(StorageDomainValidator storageDomainValidator, List<DiskImage> disksList) { for (DiskImage diskImage : disksList) { List<DiskImage> snapshots = diskImageDao.getAllSnapshotsForLeaf(diskImage.getImageId()); diskImage.getSnapshots().addAll(snapshots); } return validate(storageDomainValidator.hasSpaceForClonedDisks(disksList)); } /** * Logs error if one or more active domains are missing for disk images */ protected abstract void logErrorOneOrMoreActiveDomainsAreMissing(); /** * Returns collection of DiskImage objects to use for construction of the imageToDestinationDomainMap */ protected Collection<DiskImage> getDiskImagesToBeCloned() { return getAdjustedDiskImagesFromConfiguration(); } protected abstract Collection<DiskImage> getAdjustedDiskImagesFromConfiguration(); protected DiskImage getDiskImageToRemoveByParam(MoveOrCopyImageGroupParameters param) { Guid imageGroupId = param.getDestImageGroupId(); Guid imageId = param.getDestinationImageId(); DiskImage diskImage = new DiskImage(); diskImage.setId(imageGroupId); diskImage.setImageId(imageId); return diskImage; } @Override protected void executeVmCommand() { super.executeVmCommand(); setVm(null); getVm().setVmtGuid(VmTemplateHandler.BLANK_VM_TEMPLATE_ID); vmStaticDao.update(getVm().getStaticData()); } @Override protected boolean checkTemplateImages(List<String> reasons) { return true; } @Override protected abstract Guid getStoragePoolIdFromSourceImageContainer(); @Override protected boolean shouldCheckSpaceInStorageDomains() { return !getImagesToCheckDestinationStorageDomains().isEmpty(); } @Override public List<PermissionSubject> getPermissionCheckSubjects() { List<PermissionSubject> permissionList = new ArrayList<>(); permissionList.add(new PermissionSubject(getClusterId(), VdcObjectType.Cluster, getActionType().getActionGroup())); for (DiskImage disk : getParameters().getDiskInfoDestinationMap().values()) { if (disk.getStorageIds() != null && !disk.getStorageIds().isEmpty()) { permissionList.add(new PermissionSubject(disk.getStorageIds().get(0), VdcObjectType.Storage, ActionGroup.CREATE_DISK)); } } addPermissionSubjectForAdminLevelProperties(permissionList); return permissionList; } @Override protected List<? extends VmNic> getVmInterfaces() { if (_vmInterfaces == null) { _vmInterfaces = getVmFromConfiguration().getInterfaces(); } return _vmInterfaces; } @Override protected void addVmNetwork() { VnicProfileHelper vnicProfileHelper = new VnicProfileHelper(getClusterId(), getStoragePoolId(), AuditLogType.ADD_VM_FROM_SNAPSHOT_INVALID_INTERFACES); for (VmNetworkInterface iface : getVmFromConfiguration().getInterfaces()) { vnicProfileHelper.updateNicWithVnicProfileForUser(iface, getCurrentUser()); } vnicProfileHelper.auditInvalidInterfaces(getVmName()); super.addVmNetwork(); } @Override protected boolean addVmImages() { int numberOfStartedCopyTasks = 0; List<DiskImage> cinderDisks = new ArrayList<>(); try { if (!getAdjustedDiskImagesFromConfiguration().isEmpty()) { lockEntities(); for (DiskImage diskImage : getAdjustedDiskImagesFromConfiguration()) { // For illegal image check if it was snapshot as illegal (therefore // still exists at DB, or was it erased after snapshot - therefore the // query returned to UI an illegal image) if (diskImage.getImageStatus() == ImageStatus.ILLEGAL) { DiskImage snapshotImageInDb = diskImageDao.getSnapshotById(diskImage.getImageId()); if (snapshotImageInDb == null) { // If the snapshot diskImage is null, it means the disk was probably // erased after the snapshot was created. // Create a disk to reflect the fact the disk existed during snapshot saveIllegalDisk(diskImage); } } else {// Only legal images can be copied if (diskImage.getDiskStorageType() == DiskStorageType.CINDER) { CinderDisk cinder = (CinderDisk) diskImage; cinder.setVmSnapshotId(getVmSnapshotId()); cinderDisks.add(cinder); continue; } copyDiskImage(diskImage, diskImage.getStorageIds().get(0), diskInfoDestinationMap.get(diskImage.getId()).getStorageIds().get(0), diskInfoDestinationMap.get(diskImage.getId()).getDiskProfileId(), getActionType()); numberOfStartedCopyTasks++; } } addVmCinderDisks(cinderDisks); } } finally { // If no tasks were created, endAction will not be called, but // it is still needed to unlock the entities if ((numberOfStartedCopyTasks == 0) && cinderDisks.isEmpty()) { unlockEntities(); } } return true; } private void saveIllegalDisk(final DiskImage diskImage) { TransactionSupport.executeInNewTransaction(() -> { // Allocating new IDs for image and disk as it's possible // that more than one clone will be made from this source // So this is required to avoid PK violation at DB. diskImage.setImageId(Guid.newGuid()); diskImage.setId(Guid.newGuid()); diskImage.setParentId(Guid.Empty); diskImage.setImageTemplateId(Guid.Empty); ImagesHandler.setDiskAlias(diskImage, getVm()); ImagesHandler.addDiskImage(diskImage, getVmId()); return null; }); } @Override protected void copyVmDevices() { List<VmDevice> devices = new ArrayList<>(getVmFromConfiguration().getUnmanagedDeviceList()); devices.addAll(getVmFromConfiguration().getManagedVmDeviceMap().values()); getVmDeviceUtils().copyVmDevices(getSourceVmId(), getVmId(), getSourceVmFromDb().getStaticData(), getVm().getStaticData(), devices, getSrcDeviceIdToTargetDeviceIdMapping(), isSoundDeviceEnabled(), getParameters().isConsoleEnabled(), isVirtioScsiEnabled(), isBalloonEnabled(), getParameters().getGraphicsDevices().keySet(), false, getVmDeviceUtils().canCopyHostDevices(getSourceVmId(), getVm().getStaticData()), getEffectiveCompatibilityVersion()); } @Override protected boolean isDisksVolumeFormatValid() { return true; } protected abstract VM getVmFromConfiguration(); @Override protected abstract Guid getSourceVmId(); @Override protected VdcActionType getChildActionType() { return VdcActionType.CopyImageGroup; } protected abstract VM getSourceVmFromDb(); protected void unlockEntities() { } protected void lockEntities() { } }