package org.ovirt.engine.core.bll.storage.utils; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.ovirt.engine.core.bll.ValidationResult; import org.ovirt.engine.core.bll.storage.disk.DiskHandler; import org.ovirt.engine.core.bll.storage.disk.image.DisksFilter; import org.ovirt.engine.core.bll.validator.storage.MultipleDiskVmElementValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.businessentities.StorageDomain; 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.DiskVmElement; import org.ovirt.engine.core.common.businessentities.storage.LUNs; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl; import org.ovirt.engine.core.dao.DiskDao; import org.ovirt.engine.core.dao.DiskImageDao; import org.ovirt.engine.core.dao.DiskVmElementDao; import org.ovirt.engine.core.dao.StorageDomainDao; @Singleton public class BlockStorageDiscardFunctionalityHelper { @Inject private DiskDao diskDao; @Inject private DiskImageDao diskImageDao; @Inject private DiskVmElementDao diskVmElementDao; @Inject private StorageDomainDao storageDomainDao; @Inject private AuditLogDirector auditLogDirector; @Inject private DiskHandler diskHandler; public ValidationResult isExistingDiscardFunctionalityPreserved(Collection<LUNs> lunsToAdd, StorageDomain storageDomain) { if (!isExistingPassDiscardFunctionalityPreserved(lunsToAdd, storageDomain)) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_LUN_BREAKS_STORAGE_DOMAIN_PASS_DISCARD_SUPPORT, getStorageDomainNameVariableReplacement(storageDomain)); } if (!isExistingDiscardAfterDeleteFunctionalityPreserved(lunsToAdd, storageDomain)) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_LUN_BREAKS_STORAGE_DOMAIN_DISCARD_AFTER_DELETE, getStorageDomainNameVariableReplacement(storageDomain)); } return ValidationResult.VALID; } private String getStorageDomainNameVariableReplacement(StorageDomain storageDomain) { return String.format("$storageDomainName %1$s", storageDomain.getName()); } protected boolean isExistingPassDiscardFunctionalityPreserved(Collection<LUNs> lunsToAdd, StorageDomain storageDomain) { boolean sdSupportsDiscard = Boolean.TRUE.equals(storageDomain.getSupportsDiscard()); if (!sdSupportsDiscard) { // The storage domain already doesn't support discard so there's no // need to check if the new luns break the discard functionality. return true; } List<DiskImage> sdDisks = diskImageDao.getAllForStorageDomain(storageDomain.getId()); if (sdDisks.isEmpty()) { // There are no disks on the storage domain, so even if // it supports discard, no functionality will be damaged, return true; } Collection<Guid> sdDisksIds = sdDisks.stream().map(DiskImage::getId).collect(Collectors.toList()); Collection<DiskVmElement> diskVmElements = null; if (!allLunsSupportDiscard(lunsToAdd)) { // The sd supports discard and there is at least one lun that breaks its support for discard. diskVmElements = diskVmElementDao.getAllDiskVmElementsByDisksIds(sdDisksIds); if (vmDiskWithPassDiscardExists(diskVmElements)) { // There is at least one disk that is configured to pass discard // and at least one lun that breaks the support for discard. return false; } } boolean sdDiscardZeroesData = Boolean.TRUE.equals(storageDomain.getSupportsDiscardZeroesData()); if (sdDiscardZeroesData && !allLunsHaveDiscardZeroesTheDataSupport(lunsToAdd)) { // The sd supports the property that discard zeroes the data and // there is at least one lun that breaks this property. if (diskVmElements == null) { diskVmElements = diskVmElementDao.getAllDiskVmElementsByDisksIds(sdDisksIds); } if (vmDiskWithPassDiscardAndWadExists(sdDisks, diskVmElements)) { // There is at least one disk that requires the storage domain's support for the // property that discard zeroes the data and at least one lun that breaks this property. return false; } } return true; } protected boolean isExistingDiscardAfterDeleteFunctionalityPreserved(Collection<LUNs> lunsToAdd, StorageDomain storageDomain) { if (!storageDomain.isDiscardAfterDelete()) { // The storage domain's discard after delete property is already false so there's // no need to check if the new luns break the discard after delete functionality. return true; } if (!allLunsSupportDiscard(lunsToAdd)) { // The storage domain's discard after delete property is true // and there is at least one lun that doesn't support discard. return false; } return true; } public void logIfLunsBreakStorageDomainDiscardFunctionality(Collection<LUNs> luns, Guid storageDomainId) { Collection<LUNs> lunsThatBreakPassDiscardSupport = getLunsThatBreakPassDiscardSupport(luns, storageDomainId); if (!lunsThatBreakPassDiscardSupport.isEmpty()) { logLunsBrokeStorageDomainPassDiscardSupport(lunsThatBreakPassDiscardSupport, storageDomainId); } Collection<LUNs> lunsThatBreakDiscardAfterDeleteSupport = getLunsThatBreakDiscardAfterDeleteSupport(luns, storageDomainId); if (!lunsThatBreakDiscardAfterDeleteSupport.isEmpty()) { logLunsBrokeStorageDomainDiscardAfterDeleteSupport(lunsThatBreakDiscardAfterDeleteSupport, storageDomainId); } } public void logIfDisksWithIllegalPassDiscardExist(Guid vmId) { Collection<DiskImage> disks = DisksFilter.filterImageDisks(diskDao.getAllForVm(vmId)); Collection<DiskVmElement> diskVmElements = diskVmElementDao.getAllForVm(vmId); Map<Disk, DiskVmElement> diskToDiskVmElement = diskHandler.getDiskToDiskVmElementMap(disks, diskVmElements); Map<Guid, Guid> diskIdToDestSdId = disks.stream() .collect(Collectors.toMap(DiskImage::getId, diskImage -> diskImage.getStorageIds().get(0))); MultipleDiskVmElementValidator multipleDiskVmElementValidator = new MultipleDiskVmElementValidator(diskToDiskVmElement); Collection<Guid> disksWithoutSupportForPassDiscard = multipleDiskVmElementValidator .getDisksWithoutSupportForPassDiscard(diskIdToDestSdId); if (!disksWithoutSupportForPassDiscard.isEmpty()) { AuditLogable auditLog = new AuditLogableImpl(); auditLog.addCustomValue("DisksIds", disksWithoutSupportForPassDiscard.stream() .map(Guid::toString).collect(Collectors.joining(", "))); auditLogDirector.log(auditLog, AuditLogType.DISKS_WITH_ILLEGAL_PASS_DISCARD_EXIST); } } public boolean allLunsSupportDiscard(Collection<LUNs> luns) { return luns.stream().allMatch(LUNs::supportsDiscard); } protected boolean allLunsHaveDiscardZeroesTheDataSupport(Collection<LUNs> luns) { return luns.stream().allMatch(LUNs::hasDiscardZeroesTheDataSupport); } protected boolean vmDiskWithPassDiscardExists(Collection<DiskVmElement> diskVmElements) { return diskVmElements.stream().anyMatch(DiskVmElement::isPassDiscard); } protected boolean vmDiskWithPassDiscardAndWadExists(Collection<DiskImage> storageDomainDisks, Collection<DiskVmElement> storageDomainVmDisks) { Map<Guid, DiskVmElement> diskVmElementsMap = storageDomainVmDisks.stream().collect( Collectors.toMap(DiskVmElement::getDiskId, Function.identity())); return storageDomainDisks.stream().anyMatch(sdDisk -> sdDisk.isWipeAfterDelete() && diskVmElementsMap.containsKey(sdDisk.getId()) && diskVmElementsMap.get(sdDisk.getId()).isPassDiscard()); } protected Collection<LUNs> getLunsThatBreakPassDiscardSupport(Collection<LUNs> luns, Guid storageDomainId) { Collection<DiskImage> sdDisks = diskImageDao.getAllForStorageDomain(storageDomainId); Collection<Guid> sdDisksIds = sdDisks.stream().map(Disk::getId).collect(Collectors.toList()); Collection<DiskVmElement> diskVmElements = diskVmElementDao.getAllDiskVmElementsByDisksIds(sdDisksIds); boolean sdContainsVmDiskWithPassDiscard = vmDiskWithPassDiscardExists(diskVmElements); if (sdContainsVmDiskWithPassDiscard) { boolean sdContainsVmDiskWithPassDiscardAndWad = vmDiskWithPassDiscardAndWadExists(sdDisks, diskVmElements); return luns.stream() .filter(lun -> !lun.supportsDiscard() || (sdContainsVmDiskWithPassDiscardAndWad && !lun.hasDiscardZeroesTheDataSupport())) .collect(Collectors.toList()); } return Collections.emptyList(); } protected Collection<LUNs> getLunsThatBreakDiscardAfterDeleteSupport(Collection<LUNs> luns, Guid storageDomainId) { StorageDomain storageDomain = storageDomainDao.get(storageDomainId); if (storageDomain.isDiscardAfterDelete()) { return luns.stream() .filter(lun -> !lun.supportsDiscard()) .collect(Collectors.toList()); } return Collections.emptyList(); } private void logLunsBrokeStorageDomainPassDiscardSupport(Collection<LUNs> lunsThatBreakSdPassDiscardSupport, Guid storageDomainId) { AuditLogable auditLog = new AuditLogableImpl(); auditLog.setStorageDomainId(storageDomainId); auditLog.addCustomValue("LunsIds", lunsThatBreakSdPassDiscardSupport.stream().map(LUNs::getLUNId).collect(Collectors.joining(", "))); auditLogDirector.log(auditLog, AuditLogType.LUNS_BROKE_SD_PASS_DISCARD_SUPPORT); } private void logLunsBrokeStorageDomainDiscardAfterDeleteSupport( Collection<LUNs> lunsThatBreakSdDiscardAfterDeleteSupport, Guid storageDomainId) { AuditLogable auditLog = new AuditLogableImpl(); auditLog.setStorageDomainId(storageDomainId); auditLog.addCustomValue("LunsIds", lunsThatBreakSdDiscardAfterDeleteSupport.stream() .map(LUNs::getLUNId).collect(Collectors.joining(", "))); auditLogDirector.log(auditLog, AuditLogType.LUNS_BROKE_SD_DISCARD_AFTER_DELETE_SUPPORT); } }