package org.ovirt.engine.core.bll.storage.domain; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.context.CompensationContext; import org.ovirt.engine.core.bll.validator.storage.StoragePoolValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.ExtendSANStorageDomainParameters; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.StorageDomainStatus; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.storage.LUNs; 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.utils.Pair; import org.ovirt.engine.core.common.vdscommands.GetDeviceListVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.GetStorageDomainStatsVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.ResizeStorageDomainPVVDSCommandParameters; 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.dao.LunDao; import org.ovirt.engine.core.dao.StorageDomainDynamicDao; import org.ovirt.engine.core.utils.collections.MultiValueMapUtils; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class RefreshLunsSizeCommand<T extends ExtendSANStorageDomainParameters> extends StorageDomainCommandBase<T> { @Inject private StorageDomainDynamicDao storageDomainDynamicDao; @Inject private LunDao lunDao; private boolean deviceSizeVisibilityError = false; public RefreshLunsSizeCommand(Guid commandId) { super(commandId); } public RefreshLunsSizeCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected boolean validate() { if (!validate(new StoragePoolValidator(getStoragePool()).isUp())) { return false; } if (!(checkStorageDomain() && checkStorageDomainStatus(StorageDomainStatus.Active))) { return false; } if (!getStorageDomain().getStorageType().isBlockDomain()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_TYPE_ILLEGAL); } if (!checkLunsInStorageDomain(getParameters().getLunIds())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_LUNS_NOT_PART_OF_STORAGE_DOMAIN); } return true; } private boolean checkLunsInStorageDomain(List<String> lunIds) { // Get LUNs from DB getParameters().setLunsList(new ArrayList<>(lunDao.getAllForVolumeGroup(getStorageDomain().getStorage()))); Set<String> lunsSet = new HashSet<>(lunIds); for (LUNs lun : getParameters().getLunsList()) { if (lunsSet.contains(lun.getLUNId())) { // LUN is part of the storage domain lunsSet.remove(lun.getLUNId()); } } return lunsSet.isEmpty(); } @Override protected void executeCommand() { changeStorageDomainStatusInTransaction(getStorageDomain().getStoragePoolIsoMapData(), StorageDomainStatus.Locked); // Call GetDeviceList on specific LUNs on all Hosts List<String> lunsToRefresh = getParameters().getLunIds(); Map<String, List<Pair<VDS, LUNs>>> lunToVds = getDeviceListAllVds(lunsToRefresh); //Check if all hosts are seeing the same LUNs size. Map<String, List<VDS>> lunToFailedVDS = getFailedLuns(lunToVds); if (!lunToFailedVDS.isEmpty()) { deviceSizeVisibilityError = true; List<String> failedVds = new ArrayList<>(); for (Map.Entry<String, List<VDS>> entry : lunToFailedVDS.entrySet()) { String lunId = entry.getKey(); List<VDS> vdsList = entry.getValue(); log.error("Failed to refresh device " + lunId + " Not all VDS are seeing the same size " + "VDS :" + vdsList); String vdsListString = vdsList.stream().map(VDS::getName).collect(Collectors.joining(", ")); failedVds.add("LUN : " + lunId + "VDS: " + vdsListString); } throw new EngineException(EngineError.REFRESH_LUN_ERROR, "Failed to refresh LUNs. Not all VDS are seeing the same size: " + failedVds); } // Call PVs resize on SPM resizePVs(lunsToRefresh); List<LUNs> lunsToUpdateInDb = lunToVds.values().stream(). map(list -> list.get(0).getSecond()).collect(Collectors.toList()); updateLunsInDb(lunsToUpdateInDb); // Update storage domain size updateStorageDomainData(); changeStorageDomainStatusInTransaction(getStorageDomain().getStoragePoolIsoMapData(), StorageDomainStatus.Active); setSucceeded(true); } /** This method calls GetDeviceList with the specified luns on all hosts. In VDSM , this call will resize the devices if needed. It returns a map of LUN ID to a list of Pair(VDS,LUNs) This map will help to check if all hosts are seeing the same size of the LUNs. **/ private Map<String, List<Pair<VDS, LUNs>>> getDeviceListAllVds(List<String> lunsToResize) { Map<String, List<Pair<VDS, LUNs>>> lunToVds = new HashMap<>(); for (VDS vds : getAllRunningVdssInPool()) { GetDeviceListVDSCommandParameters parameters = new GetDeviceListVDSCommandParameters(vds.getId(), getStorageDomain().getStorageType(), false, lunsToResize); List<LUNs> luns = (List<LUNs>) runVdsCommand(VDSCommandType.GetDeviceList, parameters).getReturnValue(); for (LUNs lun : luns) { MultiValueMapUtils.addToMap(lun.getLUNId(), new Pair<>(vds, lun), lunToVds); } } return lunToVds; } protected Map<String, List<VDS>> getFailedLuns(Map<String, List<Pair<VDS, LUNs>>> lunToVds) { Map<String, List<VDS>> failedVds = new HashMap<>(); for (Map.Entry<String, List<Pair<VDS, LUNs>>> entry : lunToVds.entrySet()) { List<VDS> vdsList = new ArrayList<>(); Integer size = -1; boolean failed = false; for (Pair<VDS, LUNs> vdsSizePair : entry.getValue()) { vdsList.add(vdsSizePair.getFirst()); if (size == -1) { size = vdsSizePair.getSecond().getDeviceSize(); } else if (!size.equals(vdsSizePair.getSecond().getDeviceSize())) { failed = true; } } if (failed) { failedVds.put(entry.getKey(), vdsList); } } return failedVds; } private void resizePVs(List<String> lunsToRefresh) { for (String lun : lunsToRefresh) { Long pvSizeInBytes = resizeStorageDomainPV(lun); log.debug("PV size after resize of LUN " + lun + " :" + pvSizeInBytes + " bytes"); } } private void updateLunsInDb(List<LUNs> lunsToUpdateInDb) { TransactionSupport.executeInNewTransaction(() -> { CompensationContext context = getCompensationContext(); context.snapshotEntities(getParameters().getLunsList()); lunDao.updateAllInBatch(lunsToUpdateInDb); context.stateChanged(); return null; }); log.debug("LUNs with IDs: [" + lunsToUpdateInDb.stream().map(LUNs::getLUNId).collect(Collectors.joining(", ")) + "] were updated in the DB."); } private Long resizeStorageDomainPV(String lunId) { return (Long) runVdsCommand( VDSCommandType.ResizeStorageDomainPV, new ResizeStorageDomainPVVDSCommandParameters(getStoragePoolId(), getStorageDomainId(), lunId)).getReturnValue(); } private void updateStorageDomainData() { VDSReturnValue returnValueUpdatedStorageDomain = getStatsForDomain(); if (returnValueUpdatedStorageDomain != null) { StorageDomain updatedStorageDomain = (StorageDomain) returnValueUpdatedStorageDomain.getReturnValue(); updateStorageDomain(updatedStorageDomain); } else { log.error("Failed to update Storage Domain Data."); } } protected VDSReturnValue getStatsForDomain() { Optional<VDS> vds = getAllRunningVdssInPool().stream().findFirst(); if (!vds.isPresent()) { return null; } return runVdsCommand(VDSCommandType.GetStorageDomainStats, new GetStorageDomainStatsVDSCommandParameters(vds.get().getId(), getParameters().getStorageDomainId())); } protected void updateStorageDomain(final StorageDomain storageDomainToUpdate) { executeInNewTransaction(() -> { CompensationContext context = getCompensationContext(); context.snapshotEntity(getStorageDomain().getStorageDynamicData()); storageDomainDynamicDao.update(storageDomainToUpdate.getStorageDynamicData()); getCompensationContext().stateChanged(); return null; }); } @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? AuditLogType.USER_REFRESH_LUN_STORAGE_DOMAIN : deviceSizeVisibilityError ? AuditLogType.USER_REFRESH_LUN_STORAGE_DIFFERENT_SIZE_DOMAIN_FAILED : AuditLogType.USER_REFRESH_LUN_STORAGE_DOMAIN_FAILED; } @Override protected void setActionMessageParameters() { super.setActionMessageParameters(); addValidationMessage(EngineMessage.VAR__ACTION__UPDATE); } }