package org.ovirt.engine.core.bll.storage.pool; import java.util.Collections; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.ovirt.engine.core.bll.Backend; 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.connection.StorageHelperDirector; import org.ovirt.engine.core.bll.validator.storage.StorageDomainToPoolRelationValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.StorageDomainPoolParametersBase; import org.ovirt.engine.core.common.action.StoragePoolWithStoragesParameter; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.OvfEntityData; 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.StoragePoolIsoMap; import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMapId; import org.ovirt.engine.core.common.businessentities.StoragePoolStatus; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; 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.locks.LockingGroup; import org.ovirt.engine.core.common.queries.StorageDomainsAndStoragePoolIdQueryParameters; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.utils.VersionStorageFormatUtil; import org.ovirt.engine.core.common.vdscommands.CreateStoragePoolVDSCommandParameters; 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.compat.TransactionScopeOption; 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.StoragePoolIsoMapDao; import org.ovirt.engine.core.dao.UnregisteredOVFDataDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class AddStoragePoolWithStoragesCommand<T extends StoragePoolWithStoragesParameter> extends UpdateStoragePoolCommand<T> { @Inject private StoragePoolIsoMapDao storagePoolIsoMapDao; @Inject private StorageDomainDao storageDomainDao; @Inject private StoragePoolDao storagePoolDao; @Inject private StorageDomainStaticDao storageDomainStaticDao; @Inject private UnregisteredOVFDataDao unregisteredOVFDataDao; public AddStoragePoolWithStoragesCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } /** * Constructor for command creation when compensation is applied on startup */ public AddStoragePoolWithStoragesCommand(Guid commandId) { super(commandId); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Execution); } private StorageDomain masterStorageDomain = null; VDSReturnValue retVal; @Override protected void executeCommand() { if (updateStorageDomainsInDb()) { // setting storage pool status to maintenance StoragePool storagePool = getStoragePool(); getCompensationContext().snapshotEntity(storagePool); TransactionSupport.executeInNewTransaction(() -> { getStoragePool().setStatus(StoragePoolStatus.Maintenance); getStoragePool().setStoragePoolFormatType(masterStorageDomain.getStorageFormat()); storagePoolDao.update(getStoragePool()); getCompensationContext().stateChanged(); StoragePoolStatusHandler.poolStatusChanged(getStoragePool().getId(), getStoragePool().getStatus()); return null; }); // Following code performs only read operations, therefore no need for new transaction boolean result = false; // Once we create a storage pool with multiple hosts, the engine should connect all // the hosts in the storage pool, // since the engine picks a random host to fetch all the unregistered disks. boolean isStoragePoolCreated = false; retVal = null; for (VDS vds : getAllRunningVdssInPool()) { setVds(vds); for (Guid storageDomainId : getParameters().getStorages()) { // now the domain should have the mapping // with the pool in db StorageDomain storageDomain = storageDomainDao.getForStoragePool(storageDomainId, getStoragePool().getId()); StorageHelperDirector.getInstance() .getItem(storageDomain.getStorageType()) .connectStorageToDomainByVdsId(storageDomain, getVds().getId()); } if (!isStoragePoolCreated) { retVal = addStoragePoolInIrs(); if (!retVal.getSucceeded() && retVal.getVdsError().getCode() == EngineError.StorageDomainAccessError) { log.warn("Error creating storage pool on vds '{}' - continuing", vds.getName()); continue; } // storage pool creation succeeded or failed // but didn't throw exception result = retVal.getSucceeded(); isStoragePoolCreated = true; } } setSucceeded(result); if (!result) { if (retVal != null && retVal.getVdsError().getCode() != null) { throw new EngineException(retVal.getVdsError().getCode(), retVal.getVdsError().getMessage()); } else { // throw exception to cause rollback and stop the // command throw new EngineException(EngineError.ENGINE_ERROR_CREATING_STORAGE_POOL); } } registerOvfStoreDisks(); } // Create pool phase completed, no rollback is needed here, so compensation information needs to be cleared! TransactionSupport.executeInNewTransaction(() -> { getCompensationContext().cleanupCompensationDataAfterSuccessfulCommand();//TODO MMUCHA: Dear code reviewer! I think CommandBase would(should) handle that. Please advise. return null; }); freeLock(); // if create succeeded activate if (getSucceeded()) { activateStorageDomains(); } } private boolean updateStorageDomainsInDb() { boolean result = TransactionSupport.executeInNewTransaction(() -> { for (Guid storageDomainId : getParameters().getStorages()) { StorageDomain storageDomain = storageDomainDao.get(storageDomainId); if (storageDomain != null) { StoragePoolIsoMap mapFromDB = storagePoolIsoMapDao.get(new StoragePoolIsoMapId(storageDomain.getId(), getStoragePool().getId())); boolean existingInDb = mapFromDB != null; if (existingInDb) { getCompensationContext().snapshotEntity(mapFromDB); } final StorageDomainStatic staticDomain = storageDomain.getStorageStaticData(); boolean staticDomainChanged = false; StorageFormatType requiredFormatType = VersionStorageFormatUtil.getForVersion(getStoragePool().getCompatibilityVersion()); if (staticDomain.getStorageFormat().compareTo(requiredFormatType) < 0) { if (!staticDomainChanged) { getCompensationContext().snapshotEntity(staticDomain); } staticDomain.setStorageFormat(requiredFormatType); staticDomainChanged = true; } storageDomain.setStoragePoolId(getStoragePool().getId()); if (masterStorageDomain == null && storageDomain.getStorageDomainType() == StorageDomainType.Data) { if (!staticDomainChanged) { getCompensationContext().snapshotEntity(staticDomain); } storageDomain.setStorageDomainType(StorageDomainType.Master); staticDomainChanged = true; masterStorageDomain = storageDomain; // The update of storage pool should be without compensation, // this is why we run it in a different SUPRESS transaction. updateStoragePoolMasterDomainVersionInDiffTransaction(); } if (staticDomainChanged) { storageDomainStaticDao.update(staticDomain); } storageDomain.setStatus(StorageDomainStatus.Locked); if (existingInDb) { storagePoolIsoMapDao.update(storageDomain.getStoragePoolIsoMapData()); } else { storagePoolIsoMapDao.save(storageDomain.getStoragePoolIsoMapData()); getCompensationContext().snapshotNewEntity(storageDomain.getStoragePoolIsoMapData()); } } else { return false; } } getCompensationContext().stateChanged(); return true; }); return result && masterStorageDomain != null; } /** * Save the master version out of the transaction */ private VDSReturnValue addStoragePoolInIrs() { return runVdsCommand(VDSCommandType.CreateStoragePool, new CreateStoragePoolVDSCommandParameters(getVds().getId(), getStoragePool().getId(), getStoragePool().getName(), masterStorageDomain.getId(), getParameters().getStorages(), getStoragePool() .getMasterDomainVersion())); } private boolean activateStorageDomains() { boolean returnValue = true; for (final Guid storageDomainId : getParameters().getStorages()) { StorageDomainPoolParametersBase activateParameters = new StorageDomainPoolParametersBase(storageDomainId, getStoragePool().getId()); activateParameters.setSessionId(getParameters().getSessionId()); activateParameters.setTransactionScopeOption(TransactionScopeOption.RequiresNew); returnValue = Backend.getInstance() .runInternalAction(VdcActionType.ActivateStorageDomain, activateParameters).getSucceeded(); // if activate domain failed then set domain status to inactive if (!returnValue) { TransactionSupport.executeInNewTransaction(() -> { storagePoolIsoMapDao.updateStatus( new StoragePoolIsoMapId(storageDomainId, getStoragePool().getId()), StorageDomainStatus.Inactive); return null; }); } } return returnValue; } private void registerOvfStoreDisks() { for (final Guid storageDomainId : getParameters().getStorages()) { if (storageDomainStaticDao.get(storageDomainId).getStorageDomainType().isDataDomain()) { resetOvfStoreAndUnregisteredDisks(); TransactionSupport.executeInNewTransaction(() -> { List<DiskImage> ovfStoreDiskImages = getAllOVFDisks(storageDomainId, getStoragePool().getId()); registerAllOvfDisks(ovfStoreDiskImages, storageDomainId); List<OvfEntityData> entitiesFromStorageOvfDisk = getEntitiesFromStorageOvfDisk(storageDomainId, getStoragePool().getId()); // Update unregistered entities for (Object ovf : entitiesFromStorageOvfDisk) { OvfEntityData ovfEntityData = (OvfEntityData) ovf; unregisteredOVFDataDao.removeEntity(ovfEntityData.getEntityId(), storageDomainId); unregisteredOVFDataDao.saveOVFData(ovfEntityData); log.info("Adding OVF data of entity id '{}' and entity name '{}'", ovfEntityData.getEntityId(), ovfEntityData.getEntityName()); } initUnregisteredDisksToDB(storageDomainId); return null; }); } } } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__DOMAIN); addValidationMessage(EngineMessage.VAR__ACTION__ATTACH_ACTION_TO); } private boolean checkStorageDomainsInPool() { if (!getParameters().getIsInternal()) { boolean hasData = false; StorageFormatType storageFormat = null; for (Guid storageDomainId : getParameters().getStorages()) { StorageDomain domain = storageDomainDao.get(storageDomainId); StorageDomainToPoolRelationValidator storageDomainToPoolRelationValidator = new StorageDomainToPoolRelationValidator(domain.getStorageStaticData(), getStoragePool()); if (isStorageDomainNotNull(domain) && validate(storageDomainToPoolRelationValidator.validateDomainCanBeAttachedToPool())) { if (domain.getStorageDomainType() == StorageDomainType.Data) { hasData = true; if (storageFormat == null) { storageFormat = domain.getStorageFormat(); } else if (storageFormat != domain.getStorageFormat()) { addValidationMessage(EngineMessage.ERROR_CANNOT_ADD_STORAGE_POOL_WITH_DIFFERENT_STORAGE_FORMAT); return false; } } } else { return false; } } if (!hasData) { addValidationMessage(EngineMessage.ERROR_CANNOT_ADD_STORAGE_POOL_WITHOUT_DATA_AND_ISO_DOMAINS); return false; } } return true; } @Override public AuditLogType getAuditLogTypeValue() { return getSucceeded() ? AuditLogType.USER_ATTACH_STORAGE_DOMAINS_TO_POOL : AuditLogType.USER_ATTACH_STORAGE_DOMAINS_TO_POOL_FAILED; } @Override protected boolean validate() { boolean returnValue = super.validate() && checkStoragePool() && checkStoragePoolStatus(StoragePoolStatus.Uninitialized) && initializeVds() && checkStorageDomainsInPool() && isDomainAttachedToDifferentStoragePool(); return returnValue; } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { return Collections.singletonMap(getStoragePoolId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.POOL, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } private boolean isDomainAttachedToDifferentStoragePool() { if (getStoragePool().getStatus() == StoragePoolStatus.Uninitialized) { for (Guid storageDomainId : getParameters().getStorages()) { StorageDomain domain = storageDomainDao.get(storageDomainId); if (domain.getStorageDomainType().isDataDomain() && isStorageDomainAttachedToStoragePool(domain)) { return failValidation(EngineMessage.ERROR_CANNOT_ADD_STORAGE_DOMAIN_WITH_ATTACHED_DATA_DOMAIN); } } } return true; } private boolean isStorageDomainAttachedToStoragePool(StorageDomain storageDomain) { List<StorageDomain> storageDomainList = getBackend().runInternalQuery(VdcQueryType.GetStorageDomainsWithAttachedStoragePoolGuid, new StorageDomainsAndStoragePoolIdQueryParameters(storageDomain, getStoragePoolId(), getVds().getId(), false)) .getReturnValue(); return !storageDomainList.isEmpty(); } }