package org.ovirt.engine.core.bll.storage.pool; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.MoveMacs; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.RenamedEntityInfoProvider; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.network.cluster.ManagementNetworkUtil; import org.ovirt.engine.core.bll.storage.connection.StorageHelperDirector; import org.ovirt.engine.core.bll.utils.VersionSupport; import org.ovirt.engine.core.bll.validator.NetworkValidator; import org.ovirt.engine.core.bll.validator.storage.StorageDomainToPoolRelationValidator; import org.ovirt.engine.core.bll.validator.storage.StoragePoolValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.FeatureSupported; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.StoragePoolManagementParameter; import org.ovirt.engine.core.common.businessentities.Cluster; 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.StorageDomainType; import org.ovirt.engine.core.common.businessentities.StorageFormatType; import org.ovirt.engine.core.common.businessentities.StoragePool; import org.ovirt.engine.core.common.businessentities.StoragePoolStatus; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.storage.StorageType; 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.utils.VersionStorageFormatUtil; import org.ovirt.engine.core.common.vdscommands.UpgradeStoragePoolVDSCommandParameters; 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.compat.Version; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase; import org.ovirt.engine.core.dao.ClusterDao; import org.ovirt.engine.core.dao.StorageDomainDao; import org.ovirt.engine.core.dao.StorageDomainStaticDao; import org.ovirt.engine.core.dao.StoragePoolDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.dao.network.NetworkDao; import org.ovirt.engine.core.utils.ReplacementUtils; import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute public class UpdateStoragePoolCommand<T extends StoragePoolManagementParameter> extends StoragePoolManagementCommandBase<T> implements RenamedEntityInfoProvider{ @Inject private ManagementNetworkUtil managementNetworkUtil; @Inject private MoveMacs moveMacs; @Inject private VmStaticDao vmStaticDao; @Inject private NetworkDao networkDao; @Inject private StoragePoolDao storagePoolDao; @Inject private ClusterDao clusterDao; @Inject private StorageDomainDao storageDomainDao; @Inject private StorageDomainStaticDao storageDomainStaticDao; @Inject private VdsDao vdsDao; @Inject private VmDao vmDao; /** * Constructor for command creation when compensation is applied on startup */ public UpdateStoragePoolCommand(Guid commandId) { super(commandId); } public UpdateStoragePoolCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } private StoragePool oldStoragePool; private StorageDomain masterDomainForPool; @Override protected void executeCommand() { updateQuotaCache(); copyUnchangedStoragePoolProperties(getStoragePool(), oldStoragePool); storagePoolDao.updatePartial(getStoragePool()); updateStoragePoolFormatType(); updateAllClustersMacPool(); syncLunsForBlockStorageDomains(); setSucceeded(true); } private void updateAllClustersMacPool() { final Guid newMacPoolId = getParameters().getStoragePool().getMacPoolId(); final boolean shouldSetNewMacPoolOnAllClusters = newMacPoolId != null; if (shouldSetNewMacPoolOnAllClusters) { List<Cluster> clusters = clusterDao.getAllForStoragePool(getStoragePoolId()); for (Cluster cluster : clusters) { moveMacs.updateClusterAndMoveMacs(cluster, newMacPoolId, getContext()); } } } private void syncLunsForBlockStorageDomains() { if (!FeatureSupported.discardAfterDeleteSupported(getOldStoragePool().getCompatibilityVersion()) && FeatureSupported.discardAfterDeleteSupported(getStoragePool().getCompatibilityVersion())) { // Discard was not supported, and now it should be. Collection<Guid> unSyncedStorageDomains = syncLunsForStorageDomains(storageDomainDao.getAllForStoragePool(getStoragePoolId())); if (!unSyncedStorageDomains.isEmpty()) { String unSyncedStorageDomainsList = unSyncedStorageDomains.stream() .map(Guid::toString) .collect(Collectors.joining(", ")); addCustomValue("StorageDomainsIds", unSyncedStorageDomainsList); auditLogDirector.log(this, AuditLogType.STORAGE_DOMAINS_COULD_NOT_BE_SYNCED); } } } /** * Gets a collection of storage domains and calls * SyncLunsInfoForBlockStorageDomainCommand for each active block * domain with a random active host in the dc. * @param storageDomains a collection of storage domains to sync. * @return the storage domains that could not be synced. */ protected Collection<Guid> syncLunsForStorageDomains(Collection<StorageDomain> storageDomains) { List<Callable<Pair<Boolean, Guid>>> syncLunsCommands = storageDomains.stream() .filter(storageDomain -> StorageDomainStatus.Active == storageDomain.getStatus()) .filter(storageDomain -> storageDomain.getStorageType().isBlockDomain()) .map(storageDomain -> (Callable<Pair<Boolean, Guid>>) () -> syncDomainLuns(storageDomain)) .collect(Collectors.toList()); if (syncLunsCommands.isEmpty()) { return Collections.emptyList(); } return ThreadPoolUtil.invokeAll(syncLunsCommands).stream() .filter(domainSyncedPair -> !domainSyncedPair.getFirst()) .map(Pair::getSecond) .collect(Collectors.toList()); } protected Pair<Boolean, Guid> syncDomainLuns(StorageDomain storageDomain) { return new Pair<>( StorageHelperDirector.getInstance() .getItem(storageDomain.getStorageType()).syncDomainInfo(storageDomain, null), storageDomain.getId()); } private void updateQuotaCache() { if(wasQuotaEnforcementChanged()){ getQuotaManager().removeStoragePoolFromCache(getStoragePool().getId()); } } /** * Checks whether part of the update was disabling quota enforcement on the Data Center */ private boolean wasQuotaEnforcementChanged() { return getOldStoragePool().getQuotaEnforcementType() != getStoragePool().getQuotaEnforcementType(); } private StorageFormatType updatePoolAndDomainsFormat(final Version spVersion) { final StoragePool storagePool = getStoragePool(); final StorageFormatType targetFormat = VersionStorageFormatUtil.getForVersion(spVersion); storagePool.setCompatibilityVersion(spVersion); storagePool.setStoragePoolFormatType(targetFormat); TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, () -> { storagePoolDao.updatePartial(storagePool); updateMemberDomainsFormat(targetFormat); vmStaticDao.incrementDbGenerationForAllInStoragePool(storagePool.getId()); return null; }); return targetFormat; } private void updateStoragePoolFormatType() { final StoragePool storagePool = getStoragePool(); final Guid spId = storagePool.getId(); final Version spVersion = storagePool.getCompatibilityVersion(); final Version oldSpVersion = getOldStoragePool().getCompatibilityVersion(); if (oldSpVersion.equals(spVersion)) { return; } StorageFormatType targetFormat = updatePoolAndDomainsFormat(spVersion); if (getOldStoragePool().getStatus() == StoragePoolStatus.Up) { try { // No need to worry about "reupgrading" as VDSM will silently ignore // the request. runVdsCommand(VDSCommandType.UpgradeStoragePool, new UpgradeStoragePoolVDSCommandParameters(spId, targetFormat)); } catch (EngineException e) { log.warn("Upgrade process of Storage Pool '{}' has encountered a problem due to following reason: {}", spId, e.getMessage()); auditLogDirector.log(this, AuditLogType.UPGRADE_STORAGE_POOL_ENCOUNTERED_PROBLEMS); // if we get this error we know that no update was made, so we can safely revert the db updates // and return. if (e.getVdsError() != null && e.getErrorCode() == EngineError.PoolUpgradeInProgress) { updatePoolAndDomainsFormat(oldSpVersion); return; } } } runSynchronizeOperation(new RefreshPoolSingleAsyncOperationFactory(), new ArrayList<Guid>()); } private void updateMemberDomainsFormat(StorageFormatType targetFormat) { Guid spId = getStoragePool().getId(); List<StorageDomainStatic> domains = storageDomainStaticDao.getAllForStoragePool(spId); for (StorageDomainStatic domain : domains) { StorageDomainType sdType = domain.getStorageDomainType(); if (sdType == StorageDomainType.Data || sdType == StorageDomainType.Master) { log.info("Setting storage domain '{}' (type '{}') to format '{}'", domain.getId(), sdType, targetFormat); domain.setStorageFormat(targetFormat); storageDomainStaticDao.update(domain); } } } @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? AuditLogType.USER_UPDATE_STORAGE_POOL : AuditLogType.USER_UPDATE_STORAGE_POOL_FAILED; } @Override protected void setActionMessageParameters() { super.setActionMessageParameters(); addValidationMessage(EngineMessage.VAR__ACTION__UPDATE); } @Override protected boolean validate() { if (!checkStoragePool()) { return false; } // Name related validations if (!StringUtils.equals(getOldStoragePool().getName(), getStoragePool().getName()) && !isStoragePoolUnique(getStoragePool().getName())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_POOL_NAME_ALREADY_EXIST); } if (!checkStoragePoolNameLengthValid()) { return false; } List<StorageDomainStatic> poolDomains = storageDomainStaticDao.getAllForStoragePool(getStoragePool().getId()); if (getOldStoragePool().isLocal() && !getStoragePool().isLocal() && poolDomains.stream().anyMatch(sdc -> sdc.getStorageType() == StorageType.LOCALFS)) { return failValidation(EngineMessage.ERROR_CANNOT_CHANGE_STORAGE_POOL_TYPE_WITH_LOCAL); } if (!getOldStoragePool().isLocal() && getStoragePool().isLocal()) { List<Cluster> clusters = clusterDao.getAllForStoragePool(getStoragePool().getId()); if (clusters.size() > 1) { return failValidation(EngineMessage.CLUSTER_CANNOT_ADD_MORE_THEN_ONE_HOST_TO_LOCAL_STORAGE); } List<VDS> hosts = vdsDao.getAllForStoragePool(getStoragePool().getId()); if (hosts.size() > 1) { return failValidation(EngineMessage.VDS_CANNOT_ADD_MORE_THEN_ONE_HOST_TO_LOCAL_STORAGE); } } if ( !getOldStoragePool().getCompatibilityVersion().equals(getStoragePool() .getCompatibilityVersion())) { if (!isStoragePoolVersionSupported()) { return failValidation(VersionSupport.getUnsupportedVersionMessage()); } // decreasing of compatibility version is allowed under conditions else if (getStoragePool().getCompatibilityVersion().compareTo(getOldStoragePool().getCompatibilityVersion()) < 0) { if (!poolDomains.isEmpty() && !isCompatibilityVersionChangeAllowedForDomains(poolDomains)) { return false; } List<Network> networks = networkDao.getAllForDataCenter(getStoragePoolId()); for (Network network : networks) { NetworkValidator validator = getNetworkValidator(network); validator.setDataCenter(getStoragePool()); if (!getManagementNetworkUtil().isManagementNetwork(network.getId()) || !validator.canNetworkCompatibilityBeDecreased()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_CANNOT_DECREASE_DATA_CENTER_COMPATIBILITY_VERSION); } } } else if (!checkAllClustersLevel()) { // Check all clusters has at least the same compatibility version. return false; } } StoragePoolValidator validator = createStoragePoolValidator(); return validate(validator.isNotLocalfsWithDefaultCluster()); } private boolean isCompatibilityVersionChangeAllowedForDomains(List<StorageDomainStatic> poolDomains) { List<String> formatProblematicDomains = new ArrayList<>(); for (StorageDomainStatic domainStatic : poolDomains) { StorageDomainToPoolRelationValidator attachDomainValidator = getAttachDomainValidator(domainStatic); if (!attachDomainValidator.isStorageDomainFormatCorrectForDC().isValid()) { formatProblematicDomains.add(domainStatic.getName()); } } return manageCompatibilityVersionChangeCheckResult(formatProblematicDomains); } private boolean manageCompatibilityVersionChangeCheckResult(List<String> formatProblematicDomains) { if (!formatProblematicDomains.isEmpty()) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_DECREASING_COMPATIBILITY_VERSION_CAUSES_STORAGE_FORMAT_DOWNGRADING); getReturnValue().getValidationMessages().addAll(ReplacementUtils.replaceWith("formatDowngradedDomains", formatProblematicDomains, "," , formatProblematicDomains.size())); } return formatProblematicDomains.isEmpty(); } protected StorageDomainToPoolRelationValidator getAttachDomainValidator(StorageDomainStatic domainStatic) { return new StorageDomainToPoolRelationValidator(domainStatic, getStoragePool()); } protected boolean checkAllClustersLevel() { boolean returnValue = true; List<Cluster> clusters = clusterDao.getAllForStoragePool(getStoragePool().getId()); List<String> lowLevelClusters = new ArrayList<>(); for (Cluster cluster : clusters) { if (getStoragePool().getCompatibilityVersion().compareTo(cluster.getCompatibilityVersion()) > 0) { lowLevelClusters.add(cluster.getName()); } } if (!lowLevelClusters.isEmpty()) { returnValue = false; getReturnValue().getValidationMessages().add(String.format("$ClustersList %1$s", StringUtils.join(lowLevelClusters, ","))); getReturnValue() .getValidationMessages() .add(EngineMessage.ERROR_CANNOT_UPDATE_STORAGE_POOL_COMPATIBILITY_VERSION_BIGGER_THAN_CLUSTERS .toString()); } return returnValue; } private StorageDomain getMasterDomain() { if (masterDomainForPool == null) { Guid masterId = storageDomainDao.getMasterStorageDomainIdForPool(getStoragePoolId()); if (Guid.Empty.equals(masterId)) { masterDomainForPool = storageDomainDao.get(masterId); } } return masterDomainForPool; } protected NetworkValidator getNetworkValidator(Network network) { return new NetworkValidator(vmDao, network); } protected StoragePoolValidator createStoragePoolValidator() { return new StoragePoolValidator(getStoragePool()); } protected boolean isStoragePoolVersionSupported() { return VersionSupport.checkVersionSupported(getStoragePool().getCompatibilityVersion()); } /** * Copy properties from old entity which assumed not to be available in the param object. */ private static void copyUnchangedStoragePoolProperties(StoragePool newStoragePool, StoragePool oldStoragePool) { newStoragePool.setStoragePoolFormatType(oldStoragePool.getStoragePoolFormatType()); } @Override public String getEntityType() { return VdcObjectType.StoragePool.getVdcObjectTranslation(); } @Override public String getEntityOldName() { return getOldStoragePool().getName(); } @Override public String getEntityNewName() { return getParameters().getStoragePool().getName(); } @Override public void setEntityId(AuditLogableBase logable) { logable.setStoragePoolId(getOldStoragePool().getId()); } private StoragePool getOldStoragePool() { if (oldStoragePool == null) { oldStoragePool = storagePoolDao.get(getStoragePool().getId()); } return oldStoragePool; } ManagementNetworkUtil getManagementNetworkUtil() { return managementNetworkUtil; } }