package org.ovirt.engine.core.bll.storage.domain;
import static java.util.stream.Collectors.toSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Function;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.context.CompensationContext;
import org.ovirt.engine.core.bll.profiles.DiskProfileHelper;
import org.ovirt.engine.core.bll.storage.StorageHandlingCommandBase;
import org.ovirt.engine.core.bll.storage.connection.CINDERStorageHelper;
import org.ovirt.engine.core.bll.storage.connection.IStorageHelper;
import org.ovirt.engine.core.bll.storage.connection.StorageHelperDirector;
import org.ovirt.engine.core.bll.storage.pool.RefreshStoragePoolAndDisconnectAsyncOperationFactory;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.utils.VmDeviceUtils;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.DiskProfileParameters;
import org.ovirt.engine.core.common.action.StorageDomainParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
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.StorageDomainType;
import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMap;
import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMapId;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.profiles.DiskProfile;
import org.ovirt.engine.core.common.businessentities.storage.LUNs;
import org.ovirt.engine.core.common.businessentities.storage.StorageType;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.eventqueue.Event;
import org.ovirt.engine.core.common.eventqueue.EventQueue;
import org.ovirt.engine.core.common.eventqueue.EventType;
import org.ovirt.engine.core.common.utils.Pair;
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.StoragePoolIsoMapDao;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.ovirt.engine.core.utils.transaction.TransactionMethod;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
public abstract class StorageDomainCommandBase<T extends StorageDomainParametersBase> extends
StorageHandlingCommandBase<T> {
@Inject
private EventQueue eventQueue;
@Inject
private VmDeviceUtils vmDeviceUtils;
@Inject
private DiskProfileHelper diskProfileHelper;
@Inject
protected LunHelper lunHelper;
@Inject
private StoragePoolIsoMapDao storagePoolIsoMapDao;
@Inject
private LunDao lunDao;
@Inject
private StorageDomainDao storageDomainDao;
protected StorageDomainCommandBase(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
}
/**
* Constructor for command creation when compensation is applied on startup
*/
protected StorageDomainCommandBase(Guid commandId) {
super(commandId);
}
@Override
protected boolean checkStoragePool() {
return super.checkStoragePool();
}
@Override
public Guid getStorageDomainId() {
return getParameters() != null ? !Guid.Empty.equals(getParameters().getStorageDomainId()) ? getParameters()
.getStorageDomainId() : super.getStorageDomainId() : super.getStorageDomainId();
}
protected boolean canDetachDomain(boolean isDestroyStoragePool, boolean isRemoveLast) {
return checkStoragePool()
&& checkStorageDomain()
&& checkStorageDomainStatus(StorageDomainStatus.Inactive, StorageDomainStatus.Maintenance)
&& (isMaster() || isDestroyStoragePool || checkMasterDomainIsUp())
&& isDetachAllowed(isRemoveLast)
&& isCinderStorageHasNoDisks();
}
protected boolean isDetachAllowed(final boolean isRemoveLast) {
if (getStoragePoolIsoMap() == null) {
return failValidation(EngineMessage.STORAGE_DOMAIN_NOT_ATTACHED_TO_STORAGE_POOL);
}
if (!isRemoveLast && isMaster()) {
return failValidation(EngineMessage.ERROR_CANNOT_DETACH_LAST_STORAGE_DOMAIN);
}
return true;
}
private StoragePoolIsoMap getStoragePoolIsoMap() {
return storagePoolIsoMapDao.get(new StoragePoolIsoMapId(getStorageDomain().getId(), getStoragePoolId()));
}
protected boolean isCinderStorageHasNoDisks() {
if (getStorageDomain().getStorageType() == StorageType.CINDER) {
return validate(CINDERStorageHelper.isCinderHasNoImages(getStorageDomainId()));
}
return true;
}
private boolean isMaster() {
return getStorageDomain().getStorageDomainType() == StorageDomainType.Master;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__DOMAIN);
}
protected boolean checkStorageDomainNameLengthValid() {
boolean result = true;
if (StringUtils.isNotEmpty(getStorageDomain().getStorageName()) &&
getStorageDomain().getStorageName().length() > Config
.<Integer> getValue(ConfigValues.StorageDomainNameSizeLimit)) {
addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_NAME_LENGTH_IS_TOO_LONG);
result = false;
}
return result;
}
protected boolean checkStorageDomain() {
return isStorageDomainNotNull(getStorageDomain());
}
protected boolean checkStorageDomainStatus(final StorageDomainStatus... statuses) {
return checkStorageDomainStatus(Arrays.stream(statuses).collect(toSet()));
}
protected boolean checkStorageDomainStatus(Set<StorageDomainStatus> statuses) {
boolean valid = false;
StorageDomainStatus status = getStorageDomainStatus();
if (status != null) {
valid = statuses.contains(status);
}
if (!valid) {
if (status != null && status.isStorageDomainInProcess()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED);
}
addStorageDomainStatusIllegalMessage();
}
return valid;
}
protected boolean checkStorageDomainStatusNotEqual(StorageDomainStatus status) {
boolean returnValue = false;
if (getStorageDomainStatus() != null) {
returnValue = getStorageDomainStatus() != status;
if (!returnValue) {
addStorageDomainStatusIllegalMessage();
}
}
return returnValue;
}
protected boolean checkMasterDomainIsUp() {
boolean hasUpMaster =
!storageDomainDao.getStorageDomains
(getStoragePool().getId(), StorageDomainType.Master, StorageDomainStatus.Active).isEmpty();
if (!hasUpMaster) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_MASTER_STORAGE_DOMAIN_NOT_ACTIVE);
}
return true;
}
protected void setStorageDomainStatus(StorageDomainStatus status, CompensationContext context) {
if (getStorageDomain() != null && getStorageDomain().getStoragePoolId() != null) {
StoragePoolIsoMap map = getStorageDomain().getStoragePoolIsoMapData();
if(context != null) {
context.snapshotEntityStatus(map);
}
getStorageDomain().setStatus(status);
storagePoolIsoMapDao.updateStatus(map.getId(), status);
}
}
protected boolean isLunsAlreadyInUse(List<String> lunIds) {
// Get LUNs from DB
List<LUNs> lunsFromDb = lunDao.getAll();
Set<LUNs> lunsUsedBySDs = new HashSet<>();
Set<LUNs> lunsUsedByDisks = new HashSet<>();
for (LUNs lun : lunsFromDb) {
if (lunIds.contains(lun.getLUNId())) {
if (lun.getStorageDomainId() != null) {
// LUN is already part of a storage domain
lunsUsedBySDs.add(lun);
}
if (lun.getDiskId() != null) {
// LUN is already used by a disk
lunsUsedByDisks.add(lun);
}
}
}
if (!lunsUsedBySDs.isEmpty()) {
addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_LUNS_ALREADY_PART_OF_STORAGE_DOMAINS);
Set<String> formattedIds = lunsUsedBySDs.stream()
.map(lun -> getFormattedLunId(lun, lun.getStorageDomainName()))
.collect(toSet());
addValidationMessageVariable("lunIds", StringUtils.join(formattedIds, ", "));
}
if (!lunsUsedByDisks.isEmpty()) {
addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_LUNS_ALREADY_USED_BY_DISKS);
Set<String> formattedIds = lunsUsedByDisks.stream()
.map(lun -> getFormattedLunId(lun, lun.getDiskAlias()))
.collect(toSet());
addValidationMessageVariable("lunIds", StringUtils.join(formattedIds, ", "));
}
return !lunsUsedBySDs.isEmpty() || !lunsUsedByDisks.isEmpty();
}
protected String getFormattedLunId(LUNs lun, String usedByEntityName) {
return String.format("%1$s (%2$s)", lun.getLUNId(), usedByEntityName);
}
protected List<Pair<Guid, Boolean>> connectHostsInUpToDomainStorageServer() {
return performConnectionOpOnAllUpHosts(vds -> StorageHelperDirector.getInstance().getItem(getStorageDomain().getStorageType())
.connectStorageToDomainByVdsId(getStorageDomain(), vds.getId()));
}
protected List<Pair<Guid, Boolean>> disconnectHostsInUpToDomainStorageServer() {
return performConnectionOpOnAllUpHosts(vds -> StorageHelperDirector.getInstance().getItem(getStorageDomain().getStorageType())
.disconnectStorageFromDomainByVdsId(getStorageDomain(), vds.getId()));
}
private List<Pair<Guid, Boolean>> performConnectionOpOnAllUpHosts(Function<VDS, Boolean> connectionMethod) {
List<VDS> hostsInStatusUp = getAllRunningVdssInPool();
List<Callable<Pair<Guid, Boolean>>> callables = new LinkedList<>();
for (final VDS vds : hostsInStatusUp) {
callables.add(() -> {
Pair<Guid, Boolean> toReturn = new Pair<>(vds.getId(), Boolean.FALSE);
try {
toReturn.setSecond(connectionMethod.apply(vds));
} catch (RuntimeException e) {
log.error("Failed to connect/disconnect host '{}' to storage domain (name '{}', id '{}'): {}",
vds.getName(),
getStorageDomain().getName(),
getStorageDomain().getId(),
e.getMessage());
log.debug("Exception", e);
}
return toReturn;
});
}
return ThreadPoolUtil.invokeAll(callables);
}
protected void disconnectAllHostsInPool() {
getEventQueue().submitEventSync(
new Event(getParameters().getStoragePoolId(),
getParameters().getStorageDomainId(),
null,
EventType.POOLREFRESH,
""),
() -> {
runSynchronizeOperation(new RefreshStoragePoolAndDisconnectAsyncOperationFactory());
return null;
});
}
/**
* The new master is a data domain which is preferred to be in Active/Unknown status, if selectInactiveWhenNoActiveUnknownDomains
* is set to True, an Inactive domain will be returned in case that no domain in Active/Unknown status was found.
* @return an elected master domain or null
*/
protected StorageDomain electNewMaster(boolean duringReconstruct, boolean selectInactiveWhenNoActiveUnknownDomains, boolean canChooseCurrentMasterAsNewMaster) {
if (getStoragePool() == null) {
log.warn("Cannot elect new master: storage pool not found");
return null;
}
List<StorageDomain> storageDomains = storageDomainDao.getAllForStoragePool(getStoragePool().getId());
if (storageDomains.isEmpty()) {
log.warn("Cannot elect new master, no storage domains found for pool {}", getStoragePool().getName());
return null;
}
Collections.sort(storageDomains,
Comparator.comparing(StorageDomain::getLastTimeUsedAsMaster)
.thenComparing(Comparator.comparing(StorageDomain::isLocal)));
StorageDomain newMaster = null;
StorageDomain storageDomain = getStorageDomain();
for (StorageDomain dbStorageDomain : storageDomains) {
if (dbStorageDomain.isHostedEngineStorage()) {
continue;
}
if ((storageDomain == null || (duringReconstruct || !dbStorageDomain.getId()
.equals(storageDomain.getId())))
&& ((dbStorageDomain.getStorageDomainType() == StorageDomainType.Data)
||
(canChooseCurrentMasterAsNewMaster && dbStorageDomain.getStorageDomainType() == StorageDomainType.Master))) {
if (dbStorageDomain.getStatus() == StorageDomainStatus.Active
|| dbStorageDomain.getStatus() == StorageDomainStatus.Unknown) {
newMaster = dbStorageDomain;
break;
} else if (selectInactiveWhenNoActiveUnknownDomains && newMaster == null
&& dbStorageDomain.getStatus() == StorageDomainStatus.Inactive) {
// if the found domain is inactive, we don't break to continue and look for
// active/unknown domain.
newMaster = dbStorageDomain;
}
}
}
return newMaster;
}
/**
* returns new master domain which is in Active/Unknown status
* @return an elected master domain or null
*/
protected StorageDomain electNewMaster() {
return electNewMaster(false, false, false);
}
@Override
public List<PermissionSubject> getPermissionCheckSubjects() {
List<PermissionSubject> permissionCheckSubjects = new ArrayList<>();
permissionCheckSubjects.add(new PermissionSubject(getParameters().getStorageDomainId(),
VdcObjectType.Storage,
getActionType().getActionGroup()));
return permissionCheckSubjects;
}
protected void changeStorageDomainStatusInTransaction(final StoragePoolIsoMap map,
final StorageDomainStatus status) {
changeStorageDomainStatusInTransaction(map, status, getCompensationContext());
}
private void changeStorageDomainStatusInTransaction(final StoragePoolIsoMap map,
final StorageDomainStatus status, final CompensationContext context) {
executeInNewTransaction(() -> {
context.snapshotEntityStatus(map);
map.setStatus(status);
storagePoolIsoMapDao.updateStatus(map.getId(), map.getStatus());
context.stateChanged();
return null;
});
}
protected void changeDomainStatusWithCompensation(StoragePoolIsoMap map, StorageDomainStatus compensateStatus,
StorageDomainStatus newStatus, CompensationContext context) {
map.setStatus(compensateStatus);
changeStorageDomainStatusInTransaction(map, newStatus, context);
}
private StorageDomainStatus getStorageDomainStatus() {
StorageDomainStatus status = null;
if (getStorageDomain() != null) {
status = getStorageDomain().getStatus();
}
return status;
}
private StorageDomainSharedStatus getStorageDomainSharedStatus() {
return getStorageDomain() == null ? null : getStorageDomain().getStorageDomainSharedStatus();
}
protected void addStorageDomainStatusIllegalMessage() {
addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_STATUS_ILLEGAL2);
StorageDomainStatus status = getStorageDomainStatus();
StorageDomainSharedStatus sharedStatus = getStorageDomainSharedStatus();
Object messageParameter = status;
if (status == StorageDomainStatus.Unknown && sharedStatus != null) {
// We got more informative information than "Unknown".
messageParameter = sharedStatus;
}
addValidationMessageVariable("status", messageParameter);
}
protected IStorageHelper getStorageHelper(StorageDomain storageDomain) {
return StorageHelperDirector.getInstance().getItem(storageDomain.getStorageType());
}
protected void executeInNewTransaction(TransactionMethod<?> method) {
TransactionSupport.executeInNewTransaction(method);
}
protected VmDeviceUtils getVmDeviceUtils() {
return vmDeviceUtils;
}
protected boolean isCinderStorageDomain() {
return getStorageDomain().getStorageType().isCinderDomain();
}
protected EventQueue getEventQueue() {
return eventQueue;
}
/**
* Creates default disk profile for existing storage domain.
*/
protected void createDefaultDiskProfile() {
executeInNewTransaction(() -> {
final DiskProfile diskProfile =
diskProfileHelper.createDiskProfile(getStorageDomain().getId(), getStorageDomainName());
DiskProfileParameters diskProfileParameters = new DiskProfileParameters(diskProfile, true);
runInternalActionWithTasksContext(VdcActionType.AddDiskProfile, diskProfileParameters);
getCompensationContext().snapshotNewEntity(diskProfile);
getCompensationContext().stateChanged();
return null;
});
}
}