package org.ovirt.engine.core.bll;
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.context.CommandContext;
import org.ovirt.engine.core.bll.utils.ClusterUtils;
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.RemoveVdsParameters;
import org.ovirt.engine.core.common.businessentities.StoragePool;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VDSStatus;
import org.ovirt.engine.core.common.businessentities.gluster.GlusterServerInfo;
import org.ovirt.engine.core.common.errors.EngineError;
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.RemoveVdsVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.common.vdscommands.VdsIdVDSCommandParametersBase;
import org.ovirt.engine.core.common.vdscommands.gluster.RemoveGlusterServerVDSParameters;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.StoragePoolDao;
import org.ovirt.engine.core.dao.VdsDao;
import org.ovirt.engine.core.dao.VdsDynamicDao;
import org.ovirt.engine.core.dao.VdsStaticDao;
import org.ovirt.engine.core.dao.VdsStatisticsDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.dao.gluster.GlusterBrickDao;
import org.ovirt.engine.core.dao.gluster.GlusterHooksDao;
import org.ovirt.engine.core.dao.gluster.GlusterServerDao;
import org.ovirt.engine.core.dao.gluster.GlusterVolumeDao;
import org.ovirt.engine.core.utils.lock.EngineLock;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
@NonTransactiveCommandAttribute
public class RemoveVdsCommand<T extends RemoveVdsParameters> extends VdsCommand<T> {
private AuditLogType errorType = AuditLogType.USER_FAILED_REMOVE_VDS;
private VDS upServer;
@Inject
private GlusterServerDao glusterServerDao;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private VdsStaticDao vdsStaticDao;
@Inject
private VdsDynamicDao vdsDynamicDao;
@Inject
private VdsStatisticsDao vdsStatisticsDao;
@Inject
private VdsDao vdsDao;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private GlusterBrickDao glusterBrickDao;
@Inject
private GlusterVolumeDao glusterVolumeDao;
@Inject
private GlusterHooksDao glusterHooksDao;
public RemoveVdsCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Execution);
}
@Override
protected void executeCommand() {
/**
* If upserver is null and force action is true, then don't try for gluster host remove, simply remove the host
* entry from database.
*/
if (isGlusterEnabled() && upServer != null) {
glusterHostRemove();
if (!getSucceeded()) {
return;
}
}
/**
* If the removing server is the last server in the cluster , then clear the gluster
* volumes and hooks from the database
* if not force, host remove would have failed if there were volumes, so safe to
* clean up volumes in DB.
*/
if (!clusterHasMultipleHosts()) {
removeGlusterVolumesFromDb();
removeGlusterHooksFromDb();
}
TransactionSupport.executeInNewTransaction(() -> {
removeVdsStatisticsFromDb();
removeVdsDynamicFromDb();
removeVdsStaticFromDb();
return null;
});
removeVdsFromCollection();
setSucceeded(true);
}
@Override
protected boolean validate() {
boolean returnValue = canRemoveVds(getVdsId(), getReturnValue().getValidationMessages());
StoragePool storagePool = storagePoolDao.getForVds(getParameters().getVdsId());
if (returnValue && storagePool != null && storagePool.isLocal()) {
if (!storageDomainDao.getAllForStoragePool(storagePool.getId()).isEmpty()) {
returnValue = failValidation(EngineMessage.VDS_CANNOT_REMOVE_HOST_WITH_LOCAL_STORAGE);
}
}
// Perform volume bricks on server and up server null check
if (returnValue && isGlusterEnabled()) {
upServer = glusterUtil.getUpServer(getClusterId());
if (!getParameters().isForceAction()) {
// fail if host has bricks on a volume
if (hasVolumeBricksOnServer()) {
returnValue = failValidation(EngineMessage.VDS_CANNOT_REMOVE_HOST_HAVING_GLUSTER_VOLUME);
} else if (upServer == null && clusterHasMultipleHosts()) {
// fail if there is no up server in cluster, and if host being removed is not
// the last server in cluster
addValidationMessageVariable("clusterName", getCluster().getName());
returnValue = failValidation(EngineMessage.ACTION_TYPE_FAILED_NO_UP_SERVER_FOUND);
}
} else {
// if force, cannot remove only if there are bricks on server and there is an up server.
if (hasVolumeBricksOnServer() && upServer != null) {
returnValue = failValidation(EngineMessage.VDS_CANNOT_REMOVE_HOST_HAVING_GLUSTER_VOLUME);
}
}
}
return returnValue;
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__REMOVE);
addValidationMessage(EngineMessage.VAR__TYPE__HOST);
}
@Override
public AuditLogType getAuditLogTypeValue() {
return getSucceeded() ? AuditLogType.USER_REMOVE_VDS : errorType;
}
private boolean statusLegalForRemove(VDS vds) {
return (vds.getStatus() == VDSStatus.NonResponsive) || (vds.getStatus() == VDSStatus.Maintenance)
|| (vds.getStatus() == VDSStatus.Down) || (vds.getStatus() == VDSStatus.Unassigned)
|| (vds.getStatus() == VDSStatus.InstallFailed) || (vds.getStatus() == VDSStatus.PendingApproval) || (vds
.getStatus() == VDSStatus.NonOperational) || (vds.getStatus() == VDSStatus.InstallingOS);
}
private void removeVdsFromCollection() {
runVdsCommand(VDSCommandType.RemoveVds, new RemoveVdsVDSCommandParameters(getVdsId()));
}
private void removeVdsStaticFromDb() {
vdsStaticDao.remove(getVdsId());
}
private void removeVdsDynamicFromDb() {
vdsDynamicDao.remove(getVdsId());
}
private void removeVdsStatisticsFromDb() {
vdsStatisticsDao.remove(getVdsId());
}
private boolean canRemoveVds(Guid vdsId, List<String> text) {
boolean returnValue = true;
// check if vds id is valid
VDS vds = vdsDao.get(vdsId);
if (vds == null) {
text.add(EngineMessage.VDS_INVALID_SERVER_ID.toString());
returnValue = false;
} else if (!statusLegalForRemove(vds)) {
text.add(EngineMessage.VDS_CANNOT_REMOVE_VDS_STATUS_ILLEGAL.toString());
returnValue = false;
} else if (vds.getVmCount() > 0) {
text.add(EngineMessage.VDS_CANNOT_REMOVE_VDS_DETECTED_RUNNING_VM.toString());
returnValue = false;
} else {
List<String> vmNamesPinnedToHost = vmStaticDao.getAllNamesPinnedToHost(vdsId);
if (!vmNamesPinnedToHost.isEmpty()) {
text.add(EngineMessage.ACTION_TYPE_FAILED_DETECTED_PINNED_VMS.toString());
text.add(String.format("$VmNames %s", StringUtils.join(vmNamesPinnedToHost, ',')));
returnValue = false;
}
}
return returnValue;
}
private boolean isGlusterEnabled() {
return getCluster().supportsGlusterService();
}
private boolean hasVolumeBricksOnServer() {
if (glusterBrickDao.getGlusterVolumeBricksByServerId(getVdsId()).size() > 0) {
return true;
} else {
return false;
}
}
private void removeGlusterVolumesFromDb() {
glusterVolumeDao.removeByClusterId(getClusterId());
}
private void removeGlusterHooksFromDb() {
glusterHooksDao.removeAllInCluster(getClusterId());
}
public ClusterUtils getClusterUtils() {
return ClusterUtils.getInstance();
}
private void glusterHostRemove() {
if (clusterHasMultipleHosts() && !hasVolumeBricksOnServer()) {
try (EngineLock lock = glusterUtil.acquireGlusterLockWait(getClusterId())) {
VDSReturnValue returnValue =
runVdsCommand(
VDSCommandType.RemoveGlusterServer,
new RemoveGlusterServerVDSParameters(upServer.getId(),
getVds().getHostName(),
getParameters().isForceAction()));
// If the host is already removed Cluster using Gluster CLI then we can setSucceeded to true.
setSucceeded(returnValue.getSucceeded()
|| EngineError.GlusterHostIsNotPartOfCluster == returnValue.getVdsError().getCode());
if (!getSucceeded()) {
// VDSM in 3.3 (or less) cluster will return GlusterHostRemoveFailedException
// if the host is not part of the cluster
// So if peer detach is failed, check the peer list to decide that the host is not part of the
// cluster
if (returnValue.getVdsError().getCode() == EngineError.GlusterHostRemoveFailedException) {
List<GlusterServerInfo> glusterServers = getGlusterPeers(upServer);
if (glusterServers != null) {
if (!glusterUtil.isHostExists(glusterServers, getVds())) {
setSucceeded(true);
}
}
}
if (!getSucceeded()) {
getReturnValue().getFault().setError(returnValue.getVdsError().getCode());
getReturnValue().getFault().setMessage(returnValue.getVdsError().getMessage());
errorType = AuditLogType.GLUSTER_SERVER_REMOVE_FAILED;
return;
}
}
// if last but one host in cluster, update the last host's known addresses
if (getClusterUtils().getServerCount(getClusterId()) == 2) {
removeOtherKnowAddressesForGlusterServer(upServer.getId());
}
}
}
}
private List<GlusterServerInfo> getGlusterPeers(VDS upServer) {
VDSReturnValue returnValue = runVdsCommand(VDSCommandType.GlusterServersList,
new VdsIdVDSCommandParametersBase(upServer.getId()));
if (returnValue.getSucceeded()) {
return (List<GlusterServerInfo>) returnValue.getReturnValue();
}
else {
return null;
}
}
private boolean clusterHasMultipleHosts() {
return getClusterUtils().hasMultipleServers(getClusterId());
}
private void removeOtherKnowAddressesForGlusterServer(Guid lastServerId) {
glusterServerDao.updateKnownAddresses(lastServerId, null);
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
Map<String, Pair<String, String>> locks = new HashMap<>();
locks.put(getParameters().getVdsId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.VDS,
EngineMessage.ACTION_TYPE_FAILED_OBJECT_LOCKED));
return locks;
}
}