package org.ovirt.engine.core.bll.storage.domain;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
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.storage.connection.CINDERStorageHelper;
import org.ovirt.engine.core.bll.storage.pool.AfterDeactivateSingleAsyncOperationFactory;
import org.ovirt.engine.core.bll.storage.pool.DisconnectStoragePoolAsyncOperationFactory;
import org.ovirt.engine.core.bll.storage.pool.StoragePoolStatusHandler;
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.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.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.VDSStatus;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmDynamic;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.constants.StorageConstants;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.eventqueue.Event;
import org.ovirt.engine.core.common.eventqueue.EventType;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.DeactivateStorageDomainVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.DisconnectStoragePoolVDSCommandParameters;
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.AsyncTaskDao;
import org.ovirt.engine.core.dao.CommandEntityDao;
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.VmDynamicDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.vdsbroker.irsbroker.SpmStopOnIrsVDSCommandParameters;
import org.ovirt.engine.core.vdsbroker.storage.StoragePoolDomainHelper;
@NonTransactiveCommandAttribute(forceCompensation = true)
public class DeactivateStorageDomainCommand<T extends StorageDomainPoolParametersBase> extends
StorageDomainCommandBase<T> {
@Inject
private CommandEntityDao commandEntityDao;
@Inject
private AsyncTaskDao asyncTaskDao;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private VmDynamicDao vmDynamicDao;
@Inject
private StoragePoolIsoMapDao storagePoolIsoMapDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private VdsDao vdsDao;
@Inject
private StorageDomainStaticDao storageDomainStaticDao;
@Inject
private VmDao vmDao;
private boolean isLastMaster;
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
Scope scope = getParameters().isSkipLock() ? Scope.None : Scope.Execution;
return lockProperties.withScope(scope);
}
public DeactivateStorageDomainCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
/**
* Constructor for command creation when compensation is applied on startup
*/
public DeactivateStorageDomainCommand(Guid commandId) {
super(commandId);
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__DOMAIN);
addValidationMessage(EngineMessage.VAR__ACTION__DEACTIVATE);
}
@Override
protected boolean validate() {
if (getParameters().isSkipChecks()) {
return true;
}
if (!checkStorageDomain()) {
return false;
}
if (!validateDomainStatus()) {
return false;
}
if (!getParameters().getIsInternal()) {
if (getStorageDomain().isHostedEngineStorage()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_HOSTED_ENGINE_STORAGE);
}
if (getStorageDomain().getStorageDomainType() == StorageDomainType.Master
&& !validateMasterDeactivationAllowed()) {
return false;
}
if (!isNoRunningVmsWithLeasesExist()) {
return false;
}
}
if (!isRunningVmsWithIsoAttached()) {
return false;
}
if (!getParameters().getIsInternal() &&
!vmDao.getAllActiveForStorageDomain(getStorageDomain().getId()).isEmpty()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_DETECTED_ACTIVE_VMS);
}
if (getStoragePool().getSpmVdsId() != null) {
// In case there are running tasks in the pool, it is impossible to deactivate the master storage domain
if (getStorageDomain().getStorageDomainType() == StorageDomainType.Master &&
asyncTaskDao.getAsyncTaskIdsByStoragePoolId(getStorageDomain().getStoragePoolId()).size() > 0) {
return failValidation(EngineMessage.ERROR_CANNOT_DEACTIVATE_MASTER_DOMAIN_WITH_TASKS_ON_POOL);
} else if (getStorageDomain().getStorageDomainType() != StorageDomainType.ISO &&
!getParameters().getIsInternal()
&& (asyncTaskDao.getAsyncTaskIdsByEntity(getParameters().getStorageDomainId()).size() > 0 ||
commandEntityDao.getCommandIdsByEntity(getParameters().getStorageDomainId()).size() > 0)) {
return failValidation(EngineMessage.ERROR_CANNOT_DEACTIVATE_DOMAIN_WITH_TASKS);
}
}
return true;
}
protected boolean validateDomainStatus() {
// Internal execution means that the domain status is being set to Inactive - therefore it's applicable for
// the Active/Unknown statuses.
// On user initiated execution, we allow to deactivate domain which is monitored.
if (!((getParameters().getIsInternal() && checkStorageDomainStatus(StorageDomainStatus.Active,
StorageDomainStatus.Unknown))
|| checkStorageDomainStatus(StorageConstants.monitoredDomainStatuses))) {
return false;
}
return true;
}
private boolean isNoRunningVmsWithLeasesExist() {
List<VmStatic> runningVmsWithLeases = vmStaticDao.getAllRunningWithLeaseOnStorageDomain(getStorageDomain().getId());
if (!runningVmsWithLeases.isEmpty()) {
String vmNames = runningVmsWithLeases.stream().map(VmStatic::getName).collect(Collectors.joining(", "));
return failValidation(EngineMessage.ERROR_CANNOT_DEACTIVATE_DOMAIN_WITH_RUNNING_VMS_WITH_LEASES,
String.format("$vmNames %s", vmNames));
}
return true;
}
private boolean validateMasterDeactivationAllowed() {
List<StorageDomain> domains =
storageDomainDao.getAllForStoragePool(getStorageDomain().getStoragePoolId());
List<StorageDomain> activeDomains = filterActiveDomains(domains);
List<StorageDomain> dataDomains = activeDomains.stream()
.filter(d -> d.getStorageDomainType() == StorageDomainType.Data).collect(Collectors.toList());
if (!activeDomains.isEmpty() && dataDomains.isEmpty()) {
return failValidation(EngineMessage.ERROR_CANNOT_DEACTIVATE_MASTER_WITH_NON_DATA_DOMAINS);
}
List<StorageDomain> busyDomains = domains.stream()
.filter(d -> d.getStatus().isStorageDomainInProcess()).collect(Collectors.toList());
if (!busyDomains.isEmpty()) {
return failValidation(EngineMessage.ERROR_CANNOT_DEACTIVATE_MASTER_WITH_LOCKED_DOMAINS);
}
return true;
}
protected boolean isRunningVmsWithIsoAttached() {
if (!getParameters().getIsInternal() && getStorageDomain().getStorageDomainType() == StorageDomainType.ISO) {
List<String> vmNames = getVmsWithAttachedISO();
if (!vmNames.isEmpty()) {
return failValidation(EngineMessage.ERROR_CANNOT_DEACTIVATE_STORAGE_DOMAIN_WITH_ISO_ATTACHED,
String.format("$VmNames %1$s", StringUtils.join(vmNames, ",")));
}
}
return true;
}
protected List<String> getVmsWithAttachedISO() {
List<VmStatic> vms = vmStaticDao.getAllByStoragePoolId(getStorageDomain().getStoragePoolId());
List<String> vmNames = new LinkedList<>();
for (VmStatic vmStatic : vms) {
VmDynamic vmDynamic = vmDynamicDao.get(vmStatic.getId());
if (vmDynamic.getStatus() != VMStatus.Down && !StringUtils.isEmpty(vmDynamic.getCurrentCd())) {
vmNames.add(vmStatic.getName());
}
}
return vmNames;
}
/**
* Filter the active domains excluding the domain which is the parameter for this command from the given domains list.
*
* @param domains
* The domains to filter.
*
* @return The active domains in the list excluding the current domain of the command.
*/
private List<StorageDomain> filterActiveDomains(List<StorageDomain> domains) {
return domains.stream()
.filter(d -> d.getStatus() == StorageDomainStatus.Active && !d.getId().equals(getStorageDomain().getId()))
.collect(Collectors.toList());
}
@Override
protected void executeCommand() {
if (isCinderStorageDomain()) {
deactivateCinderStorageDomain();
return;
}
final StoragePoolIsoMap map =
storagePoolIsoMapDao.get
(new StoragePoolIsoMapId(getParameters().getStorageDomainId(),
getParameters().getStoragePoolId()));
map.setStatus(StorageDomainStatus.Unknown);
changeStorageDomainStatusInTransaction(map,
getParameters().isInactive() ? StorageDomainStatus.Locked : StorageDomainStatus.PreparingForMaintenance);
final StorageDomain newMaster;
if (getStorageDomain().getStorageDomainType() == StorageDomainType.Master) {
newMaster = electNewMaster();
isLastMaster = proceedStorageDomainTreatmentByDomainType(newMaster, true);
} else {
newMaster = null;
isLastMaster = false;
}
final Guid newMasterId = newMaster != null ? newMaster.getId() : Guid.Empty;
if (isLastMaster) {
executeInNewTransaction(() -> {
getCompensationContext().snapshotEntityStatus(getStoragePool());
getStoragePool().setStatus(StoragePoolStatus.Maintenance);
storagePoolDao.updateStatus(getStoragePool().getId(), getStoragePool().getStatus());
getCompensationContext().stateChanged();
return null;
});
StoragePoolStatusHandler.poolStatusChanged(getStoragePool().getId(), getStoragePool().getStatus());
getStorageDomain().getStorageDynamicData().setAvailableDiskSize(null);
getStorageDomain().getStorageDynamicData().setUsedDiskSize(null);
}
if (!getParameters().isInactive()) {
runVdsCommand(VDSCommandType.DeactivateStorageDomain,
new DeactivateStorageDomainVDSCommandParameters(getStoragePool().getId(),
getStorageDomain().getId(),
newMasterId,
getStoragePool().getMasterDomainVersion()));
}
freeLock();
VDS spm = null;
if (getStoragePool().getSpmVdsId() != null) {
spm = vdsDao.get(getStoragePool().getSpmVdsId());
}
if (isLastMaster) {
if (spm != null) {
final VDSReturnValue stopSpmReturnValue = runVdsCommand(VDSCommandType.SpmStopOnIrs,
new SpmStopOnIrsVDSCommandParameters(getStoragePool().getId()));
if (!stopSpmReturnValue.getSucceeded()) {
// no need to continue because DisconnectStoragePool will
// fail if host is SPM
log.error("Aborting execution due to failure to stop SPM");
setSucceeded(false);
return;
}
runVdsCommand(VDSCommandType.DisconnectStoragePool,
new DisconnectStoragePoolVDSCommandParameters(spm.getId(),
getStoragePool().getId(), spm.getVdsSpmId()));
}
runSynchronizeOperation(new DisconnectStoragePoolAsyncOperationFactory());
}
if (!getParameters().isInactive()) {
getEventQueue().submitEventSync(
new Event(getParameters().getStoragePoolId(), getParameters().getStorageDomainId(), null, EventType.POOLREFRESH, ""),
() -> {
runSynchronizeOperation(new AfterDeactivateSingleAsyncOperationFactory(),
isLastMaster,
newMasterId);
return null;
});
if (spm != null) {
getStorageHelper(getStorageDomain()).disconnectStorageFromDomainByVdsId(getStorageDomain(), spm.getId());
}
}
executeInNewTransaction(() -> {
if (getParameters().isInactive()) {
map.setStatus(StorageDomainStatus.Inactive);
} else if (isLastMaster) {
map.setStatus(StorageDomainStatus.Maintenance);
} else {
log.info("Domain '{}' will remain in '{}' status until deactivated on all hosts",
getStorageDomain().getId(), map.getStatus());
}
storagePoolIsoMapDao.updateStatus(map.getId(), map.getStatus());
if (newMaster != null) {
StoragePoolIsoMap mapOfNewMaster = newMaster.getStoragePoolIsoMapData();
mapOfNewMaster.setStatus(StorageDomainStatus.Active);
storagePoolIsoMapDao.updateStatus(mapOfNewMaster.getId(), mapOfNewMaster.getStatus());
}
return null;
});
if (!getParameters().isSkipChecks()) {
notifyAsyncTasks();
}
setSucceeded(true);
}
@Override
protected List<VDS> getAllRunningVdssInPool() {
Set<VDSStatus> vdsStatus = EnumSet.copyOf(StoragePoolDomainHelper.vdsDomainsActiveMonitoringStatus);
vdsStatus.addAll(StoragePoolDomainHelper.vdsDomainsMaintenanceMonitoringStatus);
return vdsDao.getAllForStoragePoolAndStatuses(getStoragePool().getId(), vdsStatus);
}
private void deactivateCinderStorageDomain() {
List<Pair<Guid, Boolean>> hostsConnectionResults = disconnectHostsInUpToDomainStorageServer();
for (Pair<Guid, Boolean> pair : hostsConnectionResults) {
if (!pair.getSecond()) {
log.error("Failed to deactivate Cinder storage domain '{}' due to secrets un-registration failure.",
getStorageDomain().getName());
StoragePoolIsoMap map = storagePoolIsoMapDao.get(new StoragePoolIsoMapId(
getParameters().getStorageDomainId(), getParameters().getStoragePoolId()));
map.setStatus(StorageDomainStatus.Inactive);
storagePoolIsoMapDao.updateStatus(map.getId(), map.getStatus());
return;
}
}
CINDERStorageHelper CINDERStorageHelper = new CINDERStorageHelper();
CINDERStorageHelper.deactivateCinderDomain(getParameters().getStorageDomainId(),
getParameters().getStoragePoolId());
setSucceeded(true);
}
/**
* Send notification to user about tasks still running at the moment when the storage got deactivated.
*/
private void notifyAsyncTasks() {
final List<Guid> asyncTasks =
asyncTaskDao.getAsyncTaskIdsByEntity(getParameters().getStorageDomainId());
if (!asyncTasks.isEmpty()) {
auditLogDirector.log(this, AuditLogType.STORAGE_DOMAIN_TASKS_ERROR);
}
}
/**
* In case of master domain this method decide if to move master to other domain or move pool to maintenance (since
* there is no master domain)
*
* @param newMaster The selected new master domain
* @param lockNewMaster If true the new master domain will be locked
* @return true if newMaster is the last master
*/
protected boolean proceedStorageDomainTreatmentByDomainType(final StorageDomain newMaster,
final boolean lockNewMaster) {
if (newMaster == null) {
return true;
}
newMaster.getStorageStaticData().setLastTimeUsedAsMaster(System.currentTimeMillis());
if (newMaster.getStorageDomainType() != StorageDomainType.Master) {
executeInNewTransaction(() -> {
StoragePoolIsoMap newMasterMap = newMaster.getStoragePoolIsoMapData();
getCompensationContext().snapshotEntityUpdated(newMaster.getStorageStaticData());
newMaster.setStorageDomainType(StorageDomainType.Master);
if (lockNewMaster) {
newMasterMap.setStatus(StorageDomainStatus.Unknown);
getCompensationContext().snapshotEntityStatus(newMasterMap);
newMaster.setStatus(StorageDomainStatus.Locked);
storagePoolIsoMapDao.updateStatus(newMasterMap.getId(), newMasterMap.getStatus());
}
updateStorageDomainStaticData(newMaster.getStorageStaticData());
getCompensationContext().snapshotEntityUpdated(getStorageDomain().getStorageStaticData());
getStorageDomain().setStorageDomainType(StorageDomainType.Data);
updateStorageDomainStaticData(getStorageDomain().getStorageStaticData());
getCompensationContext().stateChanged();
return null;
});
} else {
updateStorageDomainStaticData(newMaster.getStorageStaticData());
}
updateStoragePoolMasterDomainVersionInDiffTransaction();
return false;
}
private void updateStorageDomainStaticData(StorageDomainStatic storageDomainStatic) {
storageDomainStaticDao.update(storageDomainStatic);
}
@Override
public AuditLogType getAuditLogTypeValue() {
return getParameters().getIsInternal() ?
getSucceeded() ? AuditLogType.SYSTEM_DEACTIVATED_STORAGE_DOMAIN
: AuditLogType.SYSTEM_DEACTIVATE_STORAGE_DOMAIN_FAILED
:
getSucceeded() ?
isLastMaster ?
AuditLogType.USER_DEACTIVATED_LAST_MASTER_STORAGE_DOMAIN :
AuditLogType.USER_DEACTIVATED_STORAGE_DOMAIN
:
AuditLogType.USER_DEACTIVATE_STORAGE_DOMAIN_FAILED;
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
StorageDomain storageDomain = getStorageDomain();
if (storageDomain != null) {
Map<String, Pair<String, String>> locks = new HashMap<>();
locks.put(storageDomain.getId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
if (storageDomain.getStorageDomainType() == StorageDomainType.Master) {
locks.put(storageDomain.getStoragePoolId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.POOL, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
}
return locks;
}
return null;
}
@Override
protected Map<String, Pair<String, String>> getSharedLocks() {
StorageDomain storageDomain = getStorageDomain();
if (storageDomain != null && storageDomain.getStorageDomainType() == StorageDomainType.Data
&& storageDomain.getStoragePoolId() != null) {
return Collections.singletonMap(storageDomain.getStoragePoolId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.POOL, EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
}
return null;
}
}