package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.Collections;
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.attestationbroker.AttestThread;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.bll.pm.FenceProxyLocator;
import org.ovirt.engine.core.bll.pm.HostFenceActionExecutor;
import org.ovirt.engine.core.bll.storage.StorageHandlingCommandBase;
import org.ovirt.engine.core.bll.storage.pool.StoragePoolStatusHandler;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.action.ConnectHostToStoragePoolServersParameters;
import org.ovirt.engine.core.common.action.HostStoragePoolParametersBase;
import org.ovirt.engine.core.common.action.SetNonOperationalVdsParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdsActionParameters;
import org.ovirt.engine.core.common.businessentities.AttestationResultEnum;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.KdumpStatus;
import org.ovirt.engine.core.common.businessentities.NonOperationalReason;
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.StoragePool;
import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMap;
import org.ovirt.engine.core.common.businessentities.StoragePoolStatus;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VDSDomainsData;
import org.ovirt.engine.core.common.businessentities.VdsSpmStatus;
import org.ovirt.engine.core.common.businessentities.pm.FenceActionType;
import org.ovirt.engine.core.common.businessentities.pm.FenceOperationResult;
import org.ovirt.engine.core.common.businessentities.pm.FenceOperationResult.Status;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.VDSError;
import org.ovirt.engine.core.common.eventqueue.Event;
import org.ovirt.engine.core.common.eventqueue.EventQueue;
import org.ovirt.engine.core.common.eventqueue.EventResult;
import org.ovirt.engine.core.common.eventqueue.EventType;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.ConnectStoragePoolVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.MomPolicyVDSParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.common.vdscommands.VdsIdAndVdsVDSCommandParametersBase;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
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.VdsDynamicDao;
import org.ovirt.engine.core.dao.VdsKdumpStatusDao;
import org.ovirt.engine.core.vdsbroker.ResourceManager;
import org.ovirt.engine.core.vdsbroker.attestation.AttestationService;
import org.ovirt.engine.core.vdsbroker.attestation.AttestationValue;
import org.ovirt.engine.core.vdsbroker.irsbroker.IrsProxy;
import org.ovirt.engine.core.vdsbroker.irsbroker.IrsProxyManager;
/**
* Initialize Vds on its loading. For storages: First connect all storage
* servers to VDS. Second connect Vds to storage Pool.
*
* After server initialized - its will be moved to Up status.
*/
@NonTransactiveCommandAttribute
public class InitVdsOnUpCommand extends StorageHandlingCommandBase<HostStoragePoolParametersBase> {
private boolean fenceSucceeded = false;
private FenceOperationResult fenceStatusResult;
private boolean vdsProxyFound;
private List<StorageDomainStatic> problematicDomains;
private boolean connectPoolSucceeded;
@Inject
private AuditLogDirector auditLogDirector;
@Inject
private EventQueue eventQueue;
@Inject
private ResourceManager resourceManager;
@Inject
private InitGlusterCommandHelper glusterCommandHelper;
@Inject
private IrsProxyManager irsProxyManager;
@Inject
private VdsDynamicDao vdsDynamicDao;
@Inject
private StoragePoolIsoMapDao storagePoolIsoMapDao;
@Inject
private VdsKdumpStatusDao vdsKdumpStatusDao;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private StorageDomainStaticDao storageDomainStaticDao;
public InitVdsOnUpCommand(HostStoragePoolParametersBase parameters, CommandContext commandContext) {
super(parameters, commandContext);
setVds(parameters.getVds());
}
private boolean initTrustedService() {
List <String> hosts = new ArrayList<>();
if (AttestThread.isTrustedVds(getVds().getId())) {
return true;
}
hosts.add(getVds().getHostName());
List<AttestationValue> value = new ArrayList<>();
try {
value = AttestationService.getInstance().attestHosts(hosts);
} catch (Exception e) {
log.error("Encounter an exception while attesting host's trustworthiness for Host '{}': {}",
hosts,
e.getMessage());
log.debug("Exception", e);
}
if (value.size() > 0 && value.get(0).getTrustLevel() == AttestationResultEnum.TRUSTED) {
return true;
} else {
setNonOperational(NonOperationalReason.UNTRUSTED, null);
return false;
}
}
@Override
protected void executeCommand() {
Cluster cluster = getCluster();
boolean initSucceeded = true;
initHostKdumpDetectionStatus();
/* Host is UP, re-set the policy controlled power management flag */
getVds().setPowerManagementControlledByPolicy(true);
vdsDynamicDao.updateVdsDynamicPowerManagementPolicyFlag(
getVds().getId(),
getVds().isPowerManagementControlledByPolicy());
if (cluster.supportsTrustedService()) {
initSucceeded = initTrustedService();
}
if (initSucceeded && cluster.supportsVirtService()) {
initSucceeded = initVirtResources();
}
if (initSucceeded && cluster.supportsGlusterService()) {
initSucceeded = glusterCommandHelper.initGlusterHost(getVds());
}
setSucceeded(initSucceeded);
if (getSucceeded()) {
addCustomValue("HostStatus", getVds().getStatus().toString());
auditLogDirector.log(this, AuditLogType.VDS_DETECTED);
}
}
private boolean initVirtResources() {
resourceManager.clearLastStatusEventStampsFromVds(getVdsId());
if (initializeStorage()) {
processFence();
processStoragePoolStatus();
runUpdateMomPolicy(getCluster(), getVds());
refreshHostDeviceList();
} else {
Map<String, String> customLogValues = new HashMap<>();
customLogValues.put("StoragePoolName", getStoragePoolName());
if (problematicDomains != null && !problematicDomains.isEmpty()) {
customLogValues.put("StorageDomainNames",
problematicDomains.stream().map(StorageDomainStatic::getName).collect(Collectors.joining(", ")));
}
setNonOperational(NonOperationalReason.STORAGE_DOMAIN_UNREACHABLE, customLogValues);
return false;
}
return true;
}
private void refreshHostDeviceList() {
try {
runInternalAction(VdcActionType.RefreshHostDevices, new VdsActionParameters(getVdsId()));
} catch (EngineException e) {
log.error("Could not refresh host devices for host '{}'", getVds().getName());
}
}
private void processFence() {
vdsProxyFound = new FenceProxyLocator(getVds()).isProxyHostAvailable();
if (getVds().isPmEnabled() && vdsProxyFound) {
HostFenceActionExecutor executor = new HostFenceActionExecutor(getVds());
fenceStatusResult = executor.fence(FenceActionType.STATUS);
fenceSucceeded = fenceStatusResult.getStatus() == Status.SUCCESS;
}
}
private void processStoragePoolStatus() {
if (getVds().getSpmStatus() != VdsSpmStatus.None) {
StoragePool pool = storagePoolDao.get(getVds().getStoragePoolId());
if (pool != null && pool.getStatus() == StoragePoolStatus.NotOperational) {
pool.setStatus(StoragePoolStatus.NonResponsive);
storagePoolDao.updateStatus(pool.getId(), pool.getStatus());
StoragePoolStatusHandler.poolStatusChanged(pool.getId(), pool.getStatus());
}
}
}
private void setNonOperational(NonOperationalReason reason, Map<String, String> customLogValues) {
SetNonOperationalVdsParameters tempVar =
new SetNonOperationalVdsParameters(getVds().getId(), reason, customLogValues);
runInternalAction(VdcActionType.SetNonOperationalVds,
tempVar,
ExecutionHandler.createInternalJobContext(getContext()));
}
private boolean initializeStorage() {
boolean returnValue = false;
// if no pool or pool is uninitialized or in maintenance mode no need to
// connect any storage
if (getStoragePool() == null || StoragePoolStatus.Uninitialized == getStoragePool().getStatus()
|| StoragePoolStatus.Maintenance == getStoragePool().getStatus()) {
returnValue = true;
connectPoolSucceeded = true;
} else {
ConnectHostToStoragePoolServersParameters params = new ConnectHostToStoragePoolServersParameters(getStoragePool(), getVds());
runInternalAction(VdcActionType.ConnectHostToStoragePoolServers, params);
EventResult connectResult = connectHostToPool();
if (connectResult != null) {
returnValue = connectResult.isSuccess();
problematicDomains = (List<StorageDomainStatic>) connectResult.getResultData();
}
connectPoolSucceeded = returnValue;
}
return returnValue;
}
/**
* The following method should connect host to pool
* The method will perform a connect storage pool operation,
* if operation will wail on StoragePoolWrongMaster or StoragePoolMasterNotFound errors
* we will try to run reconstruct
*/
private EventResult connectHostToPool() {
final VDS vds = getVds();
EventResult result =
eventQueue.submitEventSync(new Event(getStoragePool().getId(),
null,
vds.getId(),
EventType.VDSCONNECTTOPOOL,
"Trying to connect host " + vds.getHostName() + " with id " + vds.getId()
+ " to the pool " + getStoragePool().getId()),
() -> runConnectHostToPoolEvent(getStoragePool().getId(), vds));
return result;
}
private EventResult runConnectHostToPoolEvent(final Guid storagePoolId, final VDS vds) {
EventResult result = new EventResult(true, EventType.VDSCONNECTTOPOOL);
StoragePool storagePool = storagePoolDao.get(storagePoolId);
StorageDomain masterDomain = storageDomainDao.getStorageDomains(storagePoolId, StorageDomainType.Master).get(0);
List<StoragePoolIsoMap> storagePoolIsoMap = storagePoolIsoMapDao.getAllForStoragePool(storagePoolId);
boolean masterDomainInactiveOrUnknown = masterDomain.getStatus() == StorageDomainStatus.Inactive
|| masterDomain.getStatus() == StorageDomainStatus.Unknown;
VDSError error = null;
try {
VDSReturnValue vdsReturnValue = runVdsCommand(VDSCommandType.ConnectStoragePool,
new ConnectStoragePoolVDSCommandParameters(
vds, storagePool, masterDomain.getId(), storagePoolIsoMap));
if (!vdsReturnValue.getSucceeded()) {
error = vdsReturnValue.getVdsError();
}
} catch (EngineException e) {
error = e.getVdsError();
}
if (error != null) {
if (error.getCode() != EngineError.CannotConnectMultiplePools && masterDomainInactiveOrUnknown) {
log.info("Could not connect host '{}' to pool '{}', as the master domain is in inactive/unknown"
+ " status - not failing the operation",
vds.getName(),
storagePool.getName());
} else {
log.error("Could not connect host '{}' to pool '{}': {}",
vds.getName(),
storagePool.getName(),
error.getMessage());
result.setSuccess(false);
}
}
if (result.isSuccess()) {
Pair<Boolean, List<StorageDomainStatic>> vdsStatsResults = proceedVdsStats(!masterDomainInactiveOrUnknown, storagePool);
result.setSuccess(vdsStatsResults.getFirst());
if (!result.isSuccess()) {
result.setResultData(vdsStatsResults.getSecond());
auditLogDirector.log(this,
AuditLogType.VDS_STORAGE_VDS_STATS_FAILED);
}
}
return result;
}
private VDSReturnValue runUpdateMomPolicy(final Cluster cluster, final VDS vds) {
VDSReturnValue returnValue = new VDSReturnValue();
try {
returnValue = runVdsCommand(VDSCommandType.SetMOMPolicyParameters,
new MomPolicyVDSParameters(vds,
cluster.isEnableBallooning(),
cluster.isEnableKsm(),
cluster.isKsmMergeAcrossNumaNodes())
);
} catch (EngineException e) {
log.error("Could not update MoM policy on host '{}'", vds.getName());
returnValue.setSucceeded(false);
}
return returnValue;
}
private Pair<Boolean, List<StorageDomainStatic>> proceedVdsStats(boolean shouldCheckReportedDomains, StoragePool storagePool) {
Pair<Boolean, List<StorageDomainStatic>> returnValue = new Pair<>(true, null);
try {
runVdsCommand(VDSCommandType.GetStats, new VdsIdAndVdsVDSCommandParametersBase(getVds()));
if (shouldCheckReportedDomains) {
List<Guid> problematicDomainsIds = fetchDomainsReportedAsProblematic(getVds().getDomains(), storagePool);
for (Guid domainId : problematicDomainsIds) {
StorageDomainStatic domainInfo = storageDomainStaticDao.get(domainId);
log.error("Storage Domain '{}' of pool '{}' is in problem in host '{}'",
domainInfo != null ? domainInfo.getStorageName() : domainId,
getStoragePool().getName(),
getVds().getName());
if (domainInfo == null || domainInfo.getStorageDomainType().isDataDomain()) {
returnValue.setFirst(false);
if (returnValue.getSecond() == null) {
returnValue.setSecond(new ArrayList<>());
}
returnValue.getSecond().add(domainInfo);
}
}
}
} catch (EngineException e) {
log.error("Could not get Host statistics for Host '{}': {}",
getVds().getName(),
e.getMessage());
log.debug("Exception", e);
returnValue.setFirst(false);
}
return returnValue;
}
public List<Guid> fetchDomainsReportedAsProblematic(List<VDSDomainsData> vdsDomainsData, StoragePool storagePool) {
IrsProxy proxy = irsProxyManager.getProxy(storagePool.getId());
if (proxy != null) {
return proxy.obtainDomainsReportedAsProblematic(vdsDomainsData);
}
return Collections.emptyList();
}
@Override
public AuditLogType getAuditLogTypeValue() {
AuditLogType type = AuditLogType.UNASSIGNED;
if (getCluster().supportsVirtService()) {
if (!connectPoolSucceeded) {
type = AuditLogType.CONNECT_STORAGE_POOL_FAILED;
} else if (getVds().isPmEnabled() && fenceSucceeded) {
type = AuditLogType.VDS_FENCE_STATUS;
} else if (getVds().isPmEnabled() && !fenceSucceeded) {
type = AuditLogType.VDS_FENCE_STATUS_FAILED;
}
// PM alerts
// Check first if PM is enabled on the cluster level
if (getVds().isFencingEnabled()) {
if (getVds().isPmEnabled()) {
if (!vdsProxyFound) {
this.addCustomValue("Reason",
auditLogDirector.getMessage(AuditLogType.VDS_ALERT_FENCE_NO_PROXY_HOST));
auditLogDirector.log(this, AuditLogType.VDS_ALERT_FENCE_TEST_FAILED);
} else if (!fenceSucceeded) {
this.addCustomValue("Reason", fenceStatusResult.getMessage());
auditLogDirector.log(this, AuditLogType.VDS_ALERT_FENCE_TEST_FAILED);
}
} else {
auditLogDirector.log(this, AuditLogType.VDS_ALERT_FENCE_IS_NOT_CONFIGURED);
}
}
}
return type;
}
private void initHostKdumpDetectionStatus() {
// host is UP, remove kdump status
vdsKdumpStatusDao.remove(getVdsId());
if (getVds().isPmEnabled() &&
getVds().isPmKdumpDetection() &&
getVds().getKdumpStatus() != KdumpStatus.ENABLED) {
auditLogDirector.log(this, AuditLogType.KDUMP_DETECTION_NOT_CONFIGURED_ON_VDS);
}
}
}