package org.ovirt.engine.core.bll.storage.connection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; 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.context.CompensationContext; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.StorageServerConnectionParametersBase; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.StorageDomainSharedStatus; import org.ovirt.engine.core.common.businessentities.StorageDomainStatus; import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMap; import org.ovirt.engine.core.common.businessentities.StorageServerConnections; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.storage.LUNs; import org.ovirt.engine.core.common.businessentities.storage.StorageType; 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.GetStorageDomainStatsVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.StorageServerConnectionManagementVDSParameters; 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.StorageDomainDao; import org.ovirt.engine.core.dao.StorageDomainDynamicDao; import org.ovirt.engine.core.dao.StoragePoolIsoMapDao; import org.ovirt.engine.core.dao.StorageServerConnectionDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class UpdateStorageServerConnectionCommand<T extends StorageServerConnectionParametersBase> extends ConnectStorageToVdsCommand<T> { @Inject private StorageServerConnectionDao storageServerConnectionDao; @Inject private LunDao lunDao; @Inject private StorageDomainDao storageDomainDao; @Inject private StoragePoolIsoMapDao storagePoolIsoMapDao; @Inject private StorageDomainDynamicDao storageDomainDynamicDao; @Inject private VmDao vmDao; private List<StorageDomain> domains = new ArrayList<>(); private List<LUNs> luns = new ArrayList<>(); public UpdateStorageServerConnectionCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); } public UpdateStorageServerConnectionCommand(Guid commandId) { super(commandId); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Execution); } @Override protected boolean validate() { StorageServerConnections newConnectionDetails = getConnection(); StorageType storageType = newConnectionDetails.getStorageType(); if (!storageType.isFileDomain() && !storageType.equals(StorageType.ISCSI)) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_UNSUPPORTED_ACTION_FOR_STORAGE_TYPE); } if (!isValidConnection(newConnectionDetails)) { return false; } // Check if connection exists by id, otherwise there's nothing to update String connectionId = newConnectionDetails.getId(); StorageServerConnections oldConnection = storageServerConnectionDao.get(connectionId); if (oldConnection == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_NOT_EXIST); } if (!newConnectionDetails.getStorageType().equals(oldConnection.getStorageType())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_UNSUPPORTED_CHANGE_STORAGE_TYPE); } Guid storagePoolId = getStoragePoolIdByFileConnectionId(oldConnection.getId()); if (isConnWithSameDetailsExists(newConnectionDetails, storagePoolId)) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_ALREADY_EXISTS); } if (doDomainsUseConnection(newConnectionDetails) || doLunsUseConnection()) { if (storageType.isFileDomain() && domains.size() > 1) { String domainNames = createDomainNamesList(domains); addValidationMessageVariable("domainNames", domainNames); return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_BELONGS_TO_SEVERAL_STORAGE_DOMAINS); } // Check that the storage domain is in proper state to be edited if (!isConnectionEditable(newConnectionDetails)) { return false; } } return super.validate(); } protected String createDomainNamesList(List<StorageDomain> domains) { // Build domain names list to display in the error StringBuilder domainNames = new StringBuilder(); for (StorageDomain domain : domains) { domainNames.append(domain.getStorageName()); domainNames.append(","); } // Remove the last comma after the last domain domainNames.deleteCharAt(domainNames.length() - 1); return domainNames.toString(); } protected List<LUNs> getLuns() { if (luns.isEmpty()) { luns = lunDao.getAllForStorageServerConnection(getConnection().getId()); } return luns; } protected boolean isConnectionEditable(StorageServerConnections connection) { if (connection.getStorageType().isFileDomain()) { boolean isConnectionEditable = isFileDomainInEditState(domains.get(0)) || getParameters().isForce(); if (!isConnectionEditable) { addValidationMessageVariable("domainNames", domains.get(0).getStorageName()); addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_UNSUPPORTED_ACTION_DOMAIN_MUST_BE_IN_MAINTENANCE_OR_UNATTACHED); } return isConnectionEditable; } if (!getLuns().isEmpty()) { List<String> problematicVMNames = new ArrayList<>(); List<String> problematicDomainNames = new ArrayList<>(); for (LUNs lun : getLuns()) { Guid diskId = lun.getDiskId(); if (diskId != null) { Map<Boolean, List<VM>> vmsMap = vmDao.getForDisk(diskId, true); List<VM> pluggedVms = vmsMap.get(Boolean.TRUE); if (pluggedVms != null && !pluggedVms.isEmpty()) { for (VM vm : pluggedVms) { if (!vm.getStatus().equals(VMStatus.Down)) { problematicVMNames.add(vm.getName()); } } } } Guid storageDomainId = lun.getStorageDomainId(); if (storageDomainId != null) { StorageDomain domain = storageDomainDao.get(storageDomainId); if (!domain.getStorageDomainSharedStatus().equals(StorageDomainSharedStatus.Unattached) && !getParameters().isForce()) { for (StoragePoolIsoMap map : getStoragePoolIsoMap(domain)) { if (!map.getStatus().equals(StorageDomainStatus.Maintenance)) { String domainName = domain.getStorageName(); problematicDomainNames.add(domainName); } else { domains.add(domain); } } } else { //unattached domain, edit allowed domains.add(domain); } } } if (!problematicVMNames.isEmpty()) { if (problematicDomainNames.isEmpty()) { addValidationMessageVariable("vmNames", prepareEntityNamesForMessage(problematicVMNames)); addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_UNSUPPORTED_ACTION_FOR_RUNNING_VMS); } else { addValidationMessageVariable("vmNames", prepareEntityNamesForMessage(problematicVMNames)); addValidationMessageVariable("domainNames", prepareEntityNamesForMessage(problematicDomainNames)); addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_STORAGE_CONNECTION_UNSUPPORTED_ACTION_FOR_RUNNING_VMS_AND_DOMAINS_STATUS); } return false; } if (!problematicDomainNames.isEmpty()) { addValidationMessageVariable("domainNames", prepareEntityNamesForMessage(problematicDomainNames)); addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_UNSUPPORTED_ACTION_DOMAIN_MUST_BE_IN_MAINTENANCE_OR_UNATTACHED); return false; } } return true; } private String prepareEntityNamesForMessage(List<String> entityNames) { return StringUtils.join(entityNames, ","); } private boolean isFileDomainInEditState(StorageDomain storageDomain) { return storageDomain.getStatus() == StorageDomainStatus.Maintenance || storageDomain.getStorageDomainSharedStatus() == StorageDomainSharedStatus.Unattached; } @Override protected void executeCommand() { boolean isDomainUpdateRequired = !Guid.isNullOrEmpty(getVdsId()) && doDomainsUseConnection(getConnection()); List<StorageDomain> updatedDomains = new ArrayList<>(); boolean hasConnectStorageSucceeded = false; if (isDomainUpdateRequired) { hasConnectStorageSucceeded = connectToStorage(); VDSReturnValue returnValueUpdatedStorageDomain = null; if (hasConnectStorageSucceeded) { changeStorageDomainStatusInTransaction(StorageDomainStatus.Locked); for (StorageDomain domain : domains) { // update info such as free space - because we switched to a different server returnValueUpdatedStorageDomain = getStatsForDomain(domain); if (returnValueUpdatedStorageDomain.getSucceeded()) { StorageDomain updatedStorageDomain = (StorageDomain) returnValueUpdatedStorageDomain.getReturnValue(); updatedDomains.add(updatedStorageDomain); } } if (!updatedDomains.isEmpty()) { updateStorageDomain(updatedDomains); } } } storageServerConnectionDao.update(getParameters().getStorageServerConnection()); if (isDomainUpdateRequired) { for (StorageDomain domain : domains) { for (StoragePoolIsoMap map : getStoragePoolIsoMap(domain)) { restoreStateAfterUpdate(map); } } if (hasConnectStorageSucceeded) { disconnectFromStorage(); } } setSucceeded(true); } protected void restoreStateAfterUpdate(StoragePoolIsoMap map) { updateStatus(map, StorageDomainStatus.Maintenance); } protected boolean doDomainsUseConnection(StorageServerConnections connection) { if (domains == null || domains.isEmpty()) { domains = getStorageDomainsByConnId(connection.getId()); } return domains != null && !domains.isEmpty(); } protected boolean doLunsUseConnection() { return !getLuns().isEmpty(); } protected Collection<StoragePoolIsoMap> getStoragePoolIsoMap(StorageDomain storageDomain) { return storagePoolIsoMapDao.getAllForStorage(storageDomain.getId()); } protected void changeStorageDomainStatusInTransaction(final StorageDomainStatus status) { executeInNewTransaction(() -> { CompensationContext context = getCompensationContext(); for (StorageDomain domain : domains) { for (StoragePoolIsoMap map : getStoragePoolIsoMap(domain)) { context.snapshotEntityStatus(map); updateStatus(map, status); } } getCompensationContext().stateChanged(); return null; }); } protected void updateStorageDomain(final List<StorageDomain> storageDomainsToUpdate) { executeInNewTransaction(() -> { for (StorageDomain domainToUpdate : storageDomainsToUpdate) { CompensationContext context = getCompensationContext(); context.snapshotEntity(domainToUpdate.getStorageDynamicData()); storageDomainDynamicDao.update(domainToUpdate.getStorageDynamicData()); getCompensationContext().stateChanged(); } return null; }); } protected void updateStatus(StoragePoolIsoMap map, StorageDomainStatus status) { log.info("Setting domain '{}' to status '{}'", map.getId(), status.name()); map.setStatus(status); storagePoolIsoMapDao.updateStatus(map.getId(), map.getStatus()); } protected void executeInNewTransaction(TransactionMethod<?> method) { TransactionSupport.executeInNewTransaction(method); } protected boolean connectToStorage() { Pair<Boolean, Integer> result = connectHostToStorage(); return result.getFirst(); } protected void disconnectFromStorage() { StorageServerConnectionManagementVDSParameters connectionParametersForVdsm = createParametersForVdsm(getParameters().getVdsId(), Guid.Empty, getConnection().getStorageType(), getConnection()); boolean isDisconnectSucceeded = runVdsCommand(VDSCommandType.DisconnectStorageServer, connectionParametersForVdsm).getSucceeded(); if (!isDisconnectSucceeded) { log.warn("Failed to disconnect storage connection {}", getConnection()); } } protected VDSReturnValue getStatsForDomain(StorageDomain storageDomain) { return runVdsCommand(VDSCommandType.GetStorageDomainStats, new GetStorageDomainStatsVDSCommandParameters(getVds().getId(), storageDomain.getId())); } protected StorageServerConnectionManagementVDSParameters createParametersForVdsm(Guid vdsmId, Guid storagePoolId, StorageType storageType, StorageServerConnections storageServerConnection) { StorageServerConnectionManagementVDSParameters newConnectionParametersForVdsm = new StorageServerConnectionManagementVDSParameters(vdsmId, storagePoolId, storageType, new ArrayList<>(Arrays.asList(storageServerConnection))); return newConnectionParametersForVdsm; } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { Map<String, Pair<String, String>> locks = new HashMap<>(); domains = getStorageDomainsByConnId(getConnection().getId()); if (!domains.isEmpty()) { for (StorageDomain domain : domains) { locks.put(domain.getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } } if (getConnection().getStorageType().isFileDomain()) { // lock the path to avoid at the same time if some other user tries to // add new storage connection to same path or edit another storage server connection to point to same path locks.put(getConnection().getConnection(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE_CONNECTION, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } else { // for block domains, locking the target details locks.put(getConnection().getConnection() + ";" + getConnection().getIqn() + ";" + getConnection().getPort() + ";" + getConnection().getUserName(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE_CONNECTION, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); //lock lun disks and domains, not VMs , no need to load from db. if(getLuns()!=null) { for(LUNs lun : getLuns()) { Guid diskId = lun.getDiskId(); Guid storageDomainId = lun.getStorageDomainId(); if(diskId != null) { locks.put(diskId.toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.DISK, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } if(storageDomainId != null) { locks.put(storageDomainId.toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); } } } } // lock connection's id to avoid editing or removing this connection at the same time // by another user locks.put(getConnection().getId(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE_CONNECTION, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED)); return locks; } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__UPDATE); addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__CONNECTION); } }