package org.ovirt.engine.core.bll.storage.pool; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; 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.network.ExternalNetworkManager; import org.ovirt.engine.core.bll.storage.StorageHandlingCommandBase; import org.ovirt.engine.core.bll.storage.connection.StorageHelperDirector; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.DetachStorageDomainFromPoolParameters; import org.ovirt.engine.core.common.action.RemoveStorageDomainParameters; import org.ovirt.engine.core.common.action.StoragePoolParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.StorageDomainStatus; import org.ovirt.engine.core.common.businessentities.StorageDomainType; 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.VDSStatus; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.network.VnicProfile; import org.ovirt.engine.core.common.businessentities.storage.StorageType; 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.utils.Pair; import org.ovirt.engine.core.common.vdscommands.FormatStorageDomainVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.IrsBaseVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.compat.Guid; 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.VdsDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.network.NetworkDao; import org.ovirt.engine.core.dao.network.VmNicDao; import org.ovirt.engine.core.dao.network.VnicProfileDao; import org.ovirt.engine.core.utils.ISingleAsyncOperation; import org.ovirt.engine.core.utils.SyncronizeNumberOfAsyncOperations; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.ovirt.engine.core.vdsbroker.irsbroker.SpmStopOnIrsVDSCommandParameters; @NonTransactiveCommandAttribute(forceCompensation = true) public class RemoveStoragePoolCommand<T extends StoragePoolParametersBase> extends StorageHandlingCommandBase<T> { @Inject private NetworkDao networkDao; @Inject private VnicProfileDao vnicProfileDao; @Inject private StoragePoolIsoMapDao storagePoolIsoMapDao; @Inject private StorageDomainDao storageDomainDao; @Inject private StoragePoolDao storagePoolDao; @Inject private VmNicDao vmNicDao; @Inject private StorageDomainStaticDao storageDomainStaticDao; @Inject private VdsDao vdsDao; @Inject private VmDao vmDao; private Map<String, Pair<String, String>> sharedLocks; public RemoveStoragePoolCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } public RemoveStoragePoolCommand(Guid commandId) { super(commandId); } @Override protected void executeCommand() { removeNetworks(); // Detach master storage domain last. List<StorageDomain> storageDomains = storageDomainDao.getAllForStoragePool(getStoragePool().getId()); Collections.sort(storageDomains, Comparator.comparing(StorageDomain::getStorageDomainType)); if (storageDomains.size() > 0) { if (!getParameters().getForceDelete() && getAllRunningVdssInPool().size() > 0) { if(!regularRemoveStorageDomains(storageDomains)) { setSucceeded(false); return; } } else if (getParameters().getForceDelete()) { forceRemoveStorageDomains(storageDomains); } else { return; } } getQuotaManager().removeStoragePoolFromCache(getStoragePool().getId()); removeDataCenter(); setSucceeded(true); } private void removeDataCenter() { TransactionSupport.executeInNewTransaction(() -> { getCompensationContext().snapshotEntity(getStoragePool()); storagePoolDao.remove(getStoragePool().getId()); getCompensationContext().stateChanged(); return null; }); } private void removeNetworks() { final List<Network> networks = networkDao.getAllForDataCenter(getStoragePoolId()); for (Network network : networks) { if (network.isExternal()) { for (VmNic nic : vmNicDao.getAllForNetwork(network.getId())) { new ExternalNetworkManager(nic, network).deallocateIfExternal(); } } } TransactionSupport.executeInNewTransaction(() -> { for (final Network net : networks) { List<VnicProfile> profiles = vnicProfileDao.getAllForNetwork(net.getId()); for (VnicProfile vnicProfile : profiles) { getCompensationContext().snapshotEntity(vnicProfile); vnicProfileDao.remove(vnicProfile.getId()); } getCompensationContext().snapshotEntity(net); networkDao.remove(net.getId()); } getCompensationContext().stateChanged(); return null; }); } private void forceRemoveStorageDomains(List<StorageDomain> storageDomains) { StorageDomain masterDomain = null; for (StorageDomain storageDomain : storageDomains) { if (storageDomain.getStorageDomainType() != StorageDomainType.Master) { if (storageDomain.getStorageDomainType() != StorageDomainType.ISO) { removeDomainFromDb(storageDomain); } } else { masterDomain = storageDomain; } } if (masterDomain != null) { removeDomainFromDb(masterDomain); } } private boolean regularRemoveStorageDomains(List<StorageDomain> storageDomains) { boolean retVal = true; final StorageDomain masterDomain = storageDomains.stream().filter(s -> s.getStorageDomainType() == StorageDomainType.Master).findFirst().orElse(null); TransactionSupport.executeInNewTransaction(() -> { getCompensationContext().snapshotEntity(masterDomain.getStoragePoolIsoMapData()); masterDomain.setStatus(StorageDomainStatus.Locked); storagePoolIsoMapDao.update(masterDomain.getStoragePoolIsoMapData()); getCompensationContext().stateChanged(); return null; }); // destroying a pool is an SPM action. We need to connect all hosts // to the pool. Later on, during spm election, one of the hosts will // lock the pool // and the spm status will be FREE. Only then we can invoke the // destroy verb. connectAllHostToPoolAndDomain(masterDomain); List<VDS> vdss = getAllRunningVdssInPool(); for (StorageDomain storageDomain : storageDomains) { if (storageDomain.getStorageDomainType() != StorageDomainType.Master) { if (!removeDomainFromPool(storageDomain, vdss.get(0))) { log.error("Unable to detach storage domain '{}' '{}'", storageDomain.getStorageName(), storageDomain.getId()); retVal = false; } } } handleDestroyStoragePoolCommand(); handleMasterDomain(masterDomain); runSynchronizeOperation(new DisconnectStoragePoolAsyncOperationFactory()); setSucceeded(true); if (!getStoragePool().isLocal() || !masterDomain.isLocal()) { for (VDS vds : vdss) { StorageHelperDirector.getInstance().getItem(masterDomain.getStorageType()) .disconnectStorageFromDomainByVdsId(masterDomain, vds.getId()); } } else { try { runVdsCommand(VDSCommandType.FormatStorageDomain, new FormatStorageDomainVDSCommandParameters(vdss.get(0).getId(), masterDomain.getId())); } catch (EngineException e) { // Do nothing, exception already printed at logs } StorageHelperDirector.getInstance().getItem(masterDomain.getStorageType()) .disconnectStorageFromDomainByVdsId(masterDomain, vdss.get(0).getId()); removeDomainFromDb(masterDomain); } return retVal; } private void handleMasterDomain(StorageDomain masterDomain) { TransactionSupport.executeInNewTransaction(() -> { releaseStorageDomainMacPool(vmDao.getAllForStoragePool(getStoragePoolId())); detachStorageDomainWithEntities(masterDomain); getCompensationContext().snapshotEntity(masterDomain.getStorageStaticData()); masterDomain.setStorageDomainType(StorageDomainType.Data); storageDomainStaticDao.update(masterDomain.getStorageStaticData()); getCompensationContext().stateChanged(); return null; }); } private void handleDestroyStoragePoolCommand() { try { runVdsCommand(VDSCommandType.DestroyStoragePool, new IrsBaseVDSCommandParameters(getStoragePool().getId())); } catch (EngineException e) { try { TransactionSupport.executeInNewTransaction(() -> { runVdsCommand(VDSCommandType.SpmStopOnIrs, new SpmStopOnIrsVDSCommandParameters(getStoragePool().getId())); return null; }); } catch (Exception e1) { log.error("Failed destroy storage pool with id '{}' and after that failed to stop spm: {}", getStoragePoolId(), e1.getMessage()); log.debug("Exception", e1); } throw e; } } private void removeDomainFromDb(final StorageDomain domain) { TransactionSupport.executeInNewTransaction(() -> { // Not compensation for remove domain as we don't want // to rollback a deleted domain - it will only cause more // problems if a domain got deleted in VDSM and not in backend // as it will be impossible to remove it. StorageHelperDirector.getInstance().getItem(domain.getStorageType()) .storageDomainRemoved(domain.getStorageStaticData()); storageDomainDao.remove(domain.getId()); return null; }); } protected boolean removeDomainFromPool(StorageDomain storageDomain, VDS vds) { if (storageDomain.getStorageType() != StorageType.LOCALFS || storageDomain.getStorageDomainType() == StorageDomainType.ISO) { DetachStorageDomainFromPoolParameters tempVar = new DetachStorageDomainFromPoolParameters( storageDomain.getId(), getStoragePool().getId()); tempVar.setRemoveLast(true); tempVar.setDestroyingPool(true); // Compensation context is not passed, as we do not want to compensate in case of failure // in detach of one of storage domains if (!Backend.getInstance() .runInternalAction(VdcActionType.DetachStorageDomainFromPool, tempVar, cloneContext().withoutCompensationContext().withoutExecutionContext()) .getSucceeded()) { return false; } } else { RemoveStorageDomainParameters tempVar = new RemoveStorageDomainParameters(storageDomain.getId()); tempVar.setDestroyingPool(true); tempVar.setDoFormat(true); tempVar.setVdsId(vds.getId()); if (!runInternalAction(VdcActionType.RemoveStorageDomain, tempVar, cloneContext().withoutLock().withoutExecutionContext()) .getSucceeded()) { return false; } } return true; } @Override protected boolean validate() { if (!super.validate() || !checkStoragePool() || !checkStoragePoolStatusNotEqual(StoragePoolStatus.Up, EngineMessage.ERROR_CANNOT_REMOVE_ACTIVE_STORAGE_POOL)) { return false; } if (getStoragePool().getStatus() != StoragePoolStatus.Uninitialized && !getParameters().getForceDelete() && !initializeVds()) { return false; } final List<StorageDomain> poolDomains = storageDomainDao.getAllForStoragePool(getStoragePool().getId()); final List<StorageDomain> activeOrLockedDomains = getActiveOrLockedDomainList(poolDomains); if (!activeOrLockedDomains.isEmpty()) { return failValidation(EngineMessage.ERROR_CANNOT_REMOVE_POOL_WITH_ACTIVE_DOMAINS); } if (!getParameters().getForceDelete()) { if(poolDomains.size() > 1) { return failValidation(EngineMessage.ERROR_CANNOT_REMOVE_STORAGE_POOL_WITH_NONMASTER_DOMAINS); } if (!poolDomains.isEmpty() && !canDetachStorageDomainWithVmsAndDisks(poolDomains.get(0))) { return false; } } else { List<VDS> poolHosts = vdsDao.getAllForStoragePool(getParameters().getStoragePoolId()); sharedLocks = new HashMap<>(); for (VDS host : poolHosts) { sharedLocks.put(host.getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VDS, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } if (!poolHosts.isEmpty() && acquireLockInternal()) { for (VDS host : poolHosts) { if (host.getStatus() != VDSStatus.Maintenance) { return failValidation(EngineMessage.ERROR_CANNOT_FORCE_REMOVE_STORAGE_POOL_WITH_VDS_NOT_IN_MAINTENANCE); } } } } return true; } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__POOL); addValidationMessage(EngineMessage.VAR__ACTION__REMOVE); } protected List<StorageDomain> getActiveOrLockedDomainList(List<StorageDomain> domainsList) { return domainsList.stream() .filter(d -> d.getStatus() == StorageDomainStatus.Active || d.getStatus().isStorageDomainInProcess()) .collect(Collectors.toList()); } @Override public AuditLogType getAuditLogTypeValue() { if (getParameters().getForceDelete()){ return getSucceeded() ? AuditLogType.USER_FORCE_REMOVE_STORAGE_POOL : AuditLogType.USER_FORCE_REMOVE_STORAGE_POOL_FAILED; } return getSucceeded() ? AuditLogType.USER_REMOVE_STORAGE_POOL : AuditLogType.USER_REMOVE_STORAGE_POOL_FAILED; } /** * @param masterDomain * Connect all hosts to the pool and to the domains */ protected void connectAllHostToPoolAndDomain(final StorageDomain masterDomain) { final List<VDS> vdsList = getAllRunningVdssInPool(); final StoragePool storagePool = getStoragePool(); SyncronizeNumberOfAsyncOperations sync = new SyncronizeNumberOfAsyncOperations(vdsList.size(), null, new ActivateDeactivateSingleAsyncOperationFactory() { @Override public ISingleAsyncOperation createSingleAsyncOperation() { return new ConnectVDSToPoolAndDomains(vdsList, masterDomain, storagePool); } @Override public void initialize(List parameters) { // no need to initilalize params } }); sync.execute(); } @Override protected Map<String, Pair<String, String>> getSharedLocks() { return sharedLocks; } }