package org.ovirt.engine.core.bll.storage.domain; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import org.ovirt.engine.core.bll.InternalCommandAttribute; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.storage.utils.BlockStorageDiscardFunctionalityHelper; import org.ovirt.engine.core.bll.storage.utils.VdsCommandsHelper; import org.ovirt.engine.core.common.action.StorageDomainParametersBase; import org.ovirt.engine.core.common.businessentities.BusinessEntitiesDefinitions; import org.ovirt.engine.core.common.businessentities.StorageServerConnections; import org.ovirt.engine.core.common.businessentities.storage.LUNStorageServerConnectionMap; import org.ovirt.engine.core.common.businessentities.storage.LUNs; 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.common.vdscommands.GetVGInfoVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.LunDao; import org.ovirt.engine.core.dao.StorageServerConnectionDao; import org.ovirt.engine.core.dao.StorageServerConnectionLunMapDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; /** * Synchronize LUN details comprising the storage domain with the DB */ @InternalCommandAttribute @NonTransactiveCommandAttribute(forceCompensation = true) public class SyncLunsInfoForBlockStorageDomainCommand<T extends StorageDomainParametersBase> extends StorageDomainCommandBase<T> { @Inject private BlockStorageDiscardFunctionalityHelper discardHelper; @Inject private StorageServerConnectionDao storageServerConnectionDao; @Inject private StorageServerConnectionLunMapDao storageServerConnectionLunMapDao; @Inject private LunDao lunDao; protected final Consumer<List<LUNs>> updateExistingLuns = luns -> { lunDao.updateAll(luns); log.info("Updated LUNs information, IDs '{}'.", getLunsIdsList(luns)); }; protected final Consumer<List<LUNs>> saveNewLuns = luns -> { lunDao.saveAll(luns); log.info("New LUNs discovered, IDs '{}'", getLunsIdsList(luns)); }; protected final Consumer<List<LUNs>> noOp = luns -> {}; public SyncLunsInfoForBlockStorageDomainCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); setVdsId(parameters.getVdsId()); } public SyncLunsInfoForBlockStorageDomainCommand(Guid commandId) { super(commandId); } @Override protected void executeCommand() { final List<LUNs> lunsFromVgInfo = getLunsFromVgInfo(); final List<LUNs> lunsFromDb = lunDao.getAllForVolumeGroup(getStorageDomain().getStorage()); Map<Consumer<List<LUNs>>, List<LUNs>> lunsToUpdateInDb = getLunsToUpdateInDb(lunsFromVgInfo, lunsFromDb); boolean dbShouldBeUpdated = lunsToUpdateInDb.containsKey(updateExistingLuns) || // There are existing luns that should be updated. lunsToUpdateInDb.containsKey(saveNewLuns); // There are new luns that should be saved. if (dbShouldBeUpdated) { TransactionSupport.executeInNewTransaction(() -> { updateLunsInDb(lunsToUpdateInDb); refreshLunsConnections(lunsFromVgInfo); cleanupLunsFromDb(lunsFromVgInfo, lunsFromDb); return null; }); } setSucceeded(true); } @SuppressWarnings("unchecked") private List<LUNs> getLunsFromVgInfo() { GetVGInfoVDSCommandParameters params = new GetVGInfoVDSCommandParameters(getParameters().getVdsId(), getStorageDomain().getStorage()); if (getParameters().getVdsId() == null) { return (List<LUNs>) VdsCommandsHelper.runVdsCommandWithoutFailover( VDSCommandType.GetVGInfo, params, getStoragePoolId(), null).getReturnValue(); } return (List<LUNs>) runVdsCommand(VDSCommandType.GetVGInfo, params).getReturnValue(); } protected void refreshLunsConnections(List<LUNs> lunsFromVgInfo) { for (LUNs lunFromVgInfo : lunsFromVgInfo) { // Update lun connections map for (StorageServerConnections connection : lunFromVgInfo.getLunConnections()) { StorageServerConnections connectionFromDb = storageServerConnectionDao.getForIqn(connection.getIqn()); if (connectionFromDb == null) { // Shouldn't happen continue; } LUNStorageServerConnectionMap lunConnection = new LUNStorageServerConnectionMap( lunFromVgInfo.getLUNId(), connectionFromDb.getId()); if (storageServerConnectionLunMapDao.get(lunConnection.getId()) == null) { storageServerConnectionLunMapDao.save(lunConnection); } } } } private void cleanupLunsFromDb(List<LUNs> lunsFromVgInfo, List<LUNs> lunsFromDb) { Set<String> lunIdsFromVgInfo = lunsFromVgInfo.stream().map(LUNs::getLUNId).collect(Collectors.toSet()); List<LUNs> lunsToRemove = lunsFromDb.stream() .filter(lun -> !lun.getLUNId().startsWith(BusinessEntitiesDefinitions.DUMMY_LUN_ID_PREFIX)) .filter(lun -> !lunIdsFromVgInfo.contains(lun.getLUNId())) .peek(lun -> log.info("Removing LUN ID '{}'", lun.getLUNId())) .collect(Collectors.toList()); lunDao.removeAllInBatch(lunsToRemove); } /** * Gets a list of up to date luns from vdsm and a list of the existing luns from the db, * and returns the luns from vdsm separated into three groups: * 1. Luns that should be saved (new luns) to the db. * 2. Luns that should be updated in the db. * 3. Up to date luns that we should not do anything with. * The return value is a map from the consumer of the luns to the luns themselves. * The consumer takes the list of luns and saves/updates/does nothing with them. */ protected Map<Consumer<List<LUNs>>, List<LUNs>> getLunsToUpdateInDb(List<LUNs> lunsFromVgInfo, List<LUNs> lunsFromDb) { Map<String, LUNs> lunsFromDbMap = lunsFromDb.stream().collect(Collectors.toMap(LUNs::getLUNId, Function.identity())); return lunsFromVgInfo.stream().collect(Collectors.groupingBy(lunFromVgInfo -> { LUNs lunFromDb = lunsFromDbMap.get(lunFromVgInfo.getLUNId()); if (lunFromDb == null) { // One of the following: // 1. There's no lun in the db with the same lun id and pv id -> new lun. // 2. lunFromDb has the same pv id and a different lun id -> using storage from backup. return saveNewLuns; } boolean lunFromDbHasSamePvId = Objects.equals( lunFromDb.getPhysicalVolumeId(), lunFromVgInfo.getPhysicalVolumeId()); if (lunFromDbHasSamePvId) { // Existing lun, check if it should be updated. if (lunFromDb.getDeviceSize() != lunFromVgInfo.getDeviceSize() || !Objects.equals(lunFromDb.getDiscardMaxSize(), lunFromVgInfo.getDiscardMaxSize()) || !Objects.equals(lunFromDb.getDiscardZeroesData(), lunFromVgInfo.getDiscardZeroesData())) { return updateExistingLuns; } // Existing lun is up to date. return noOp; } // lunFromDb has the same lun id and a different pv id -> old pv id. return updateExistingLuns; })); } private static String getLunsIdsList(List<LUNs> luns) { return luns.stream().map(LUNs::getLUNId).collect(Collectors.joining(", ")); } /** * Saves the new or updates the existing luns in the DB. */ protected void updateLunsInDb(Map<Consumer<List<LUNs>>, List<LUNs>> lunsToUpdateInDbMap) { lunsToUpdateInDbMap.entrySet().forEach(entry -> entry.getKey().accept(entry.getValue())); if (lunsToUpdateInDbMap.containsKey(saveNewLuns) || lunsToUpdateInDbMap.containsKey(updateExistingLuns)) { Collection<LUNs> lunsToUpdateInDb = lunsToUpdateInDbMap.entrySet().stream() .filter(entry -> entry.getKey().equals(saveNewLuns) || entry.getKey().equals(updateExistingLuns)) .map(Map.Entry::getValue) .flatMap(List::stream) .collect(Collectors.toList()); discardHelper.logIfLunsBreakStorageDomainDiscardFunctionality(lunsToUpdateInDb, getStorageDomainId()); } } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { return Collections.singletonMap(getParameters().getStorageDomainId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.SYNC_LUNS, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } }