package org.ovirt.engine.core.bll.gluster; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; 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.CommandBase; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.interfaces.BackendInternal; import org.ovirt.engine.core.bll.utils.GlusterUtil; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VdsStatic; import org.ovirt.engine.core.common.businessentities.gluster.GlusterBrickEntity; import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeEntity; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; import org.ovirt.engine.core.common.constants.gluster.GlusterConstants; 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.VDSReturnValue; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.VdsStaticDao; import org.ovirt.engine.core.dao.gluster.GlusterBrickDao; import org.ovirt.engine.core.dao.gluster.GlusterVolumeDao; import org.ovirt.engine.core.dao.network.InterfaceDao; import org.ovirt.engine.core.dao.network.NetworkDao; /** * Base class for all Gluster commands */ public abstract class GlusterCommandBase<T extends VdcActionParametersBase> extends CommandBase<T> { protected AuditLogType errorType; protected VDS upServer; private Network glusterNetwork; @Inject protected GlusterUtil glusterUtil; @Inject protected VdsStaticDao vdsStaticDao; @Inject protected NetworkDao networkDao; @Inject protected InterfaceDao interfaceDao; @Inject protected GlusterBrickDao glusterBrickDao; @Inject protected GlusterVolumeDao glusterVolumeDao; public GlusterCommandBase(T params, CommandContext commandContext) { super(params, commandContext); } @Override protected BackendInternal getBackend() { return super.getBackend(); } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { if (!isInternalExecution()) { return Collections.singletonMap(getClusterId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.GLUSTER, EngineMessage.ACTION_TYPE_FAILED_GLUSTER_OPERATION_INPROGRESS)); } return super.getExclusiveLocks(); } /* * (non-Javadoc) * * @see org.ovirt.engine.core.bll.CommandBase#getPermissionCheckSubjects() */ @Override public List<PermissionSubject> getPermissionCheckSubjects() { // By default, check permissions at cluster level. Commands that need // more granular permissions can override this method. return Collections.singletonList(new PermissionSubject(getClusterId(), VdcObjectType.Cluster, getActionType().getActionGroup())); } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = new HashMap<>(); Cluster cluster = getCluster(); jobProperties.put(GlusterConstants.CLUSTER, cluster == null ? null : cluster.getName()); } return jobProperties; } /** * This server is chosen as random from all the Up servers. * * @return One of the servers in up status */ protected VDS getUpServer() { return glusterUtil.getRandomUpServer(getClusterId()); } @Override protected boolean validate() { if (!super.validate()) { return false; } upServer = getUpServer(); if (upServer == null) { addValidationMessageVariable("clusterName", getCluster().getName()); addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_NO_UP_SERVER_FOUND); return false; } return true; } protected void handleVdsErrors(AuditLogType errType, List<String> errors) { propagateFailure(errType, errors); // Setting Error to null to make the FrontendErrorHandler to use the Message which is being set above instead of // the EngineError.ENGINE(which will get translated to "Internal engine error") getReturnValue().getFault().setError(null); } protected void propagateFailure(AuditLogType errType, List<String> errors) { errorType = errType; getReturnValue().getExecuteFailedMessages().addAll(errors); getReturnValue().getFault().setMessage(StringUtils.join(errors, System.lineSeparator())); } protected void handleVdsError(AuditLogType errType, String error) { errorType = errType; getReturnValue().getExecuteFailedMessages().add(error); getReturnValue().getFault().setMessage(error); // Setting Error to null to make the FrontendErrorHandler to use the Message which is being set above instead of // the EngineError.ENGINE(which will get translated to "Internal engine error") getReturnValue().getFault().setError(null); } protected boolean evaluateReturnValue(AuditLogType auditLogType, VdcReturnValueBase returnValue) { boolean succeeded = returnValue.isValid(); if (!succeeded) { handleVdsErrors(auditLogType, returnValue.getValidationMessages()); } succeeded = succeeded && returnValue.getSucceeded(); if (!succeeded) { handleVdsErrors(auditLogType, returnValue.getExecuteFailedMessages()); } return succeeded; } protected boolean evaluateReturnValue(AuditLogType auditLogType, VDSReturnValue returnValue) { boolean succeeded = true; succeeded = returnValue.getSucceeded(); if (!succeeded) { handleVdsError(auditLogType, returnValue.getVdsError().getMessage()); } return succeeded; } protected boolean updateBrickServerAndInterfaceNames(List<GlusterBrickEntity> bricks, boolean addValidationMessage) { for (GlusterBrickEntity brick : bricks) { if (!updateBrickServerAndInterfaceName(brick, addValidationMessage)) { return false; } } return true; } protected boolean updateBrickServerAndInterfaceName(GlusterBrickEntity brick, boolean addValidationMessage) { VdsStatic server = vdsStaticDao.get(brick.getServerId()); if (server == null || !server.getClusterId().equals(getClusterId())) { if (addValidationMessage) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_SERVER_ID); } return false; } brick.setServerName(server.getHostName()); // No interface has been selected to use for brick- // engine will get the gluster network, if present if (brick.getNetworkId() == null) { Network network = getGlusterNetwork(); if (network != null) { brick.setNetworkId(network.getId()); brick.setNetworkAddress(getGlusterNetworkAddress(server.getId(), network.getName())); } } else { // network id has been set, update the address Network network = networkDao.get(brick.getNetworkId()); if (network != null) { brick.setNetworkAddress(getGlusterNetworkAddress(server.getId(), network.getName())); } } return true; } private Network getGlusterNetwork() { if (glusterNetwork == null) { List<Network> allNetworksInCluster = networkDao.getAllForCluster(getClusterId()); for (Network network : allNetworksInCluster) { if (network.getCluster().isGluster()) { glusterNetwork = network; return glusterNetwork; } } } return glusterNetwork; } private String getGlusterNetworkAddress(Guid hostId, String glusterNetworkName) { final List<VdsNetworkInterface> nics = interfaceDao.getAllInterfacesForVds(hostId); for (VdsNetworkInterface nic : nics) { if (glusterNetworkName.equals(nic.getNetworkName())) { return nic.getIpv4Address(); } } return null; } protected boolean validateDuplicateBricks(List<GlusterBrickEntity> newBricks) { Set<String> bricks = new HashSet<>(); for (GlusterBrickEntity brick : newBricks) { if (bricks.contains(brick.getQualifiedName())) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_DUPLICATE_BRICKS); addValidationMessageVariable("brick", brick.getQualifiedName()); return false; } bricks.add(brick.getQualifiedName()); GlusterBrickEntity existingBrick = glusterBrickDao.getBrickByServerIdAndDirectory(brick.getServerId(), brick.getBrickDirectory()); if (existingBrick != null) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_BRICK_ALREADY_EXISTS_IN_VOLUME); addValidationMessageVariable("brick", brick.getQualifiedName()); addValidationMessageVariable("volumeName", glusterVolumeDao.getById(existingBrick.getVolumeId()).getName()); return false; } } return true; } protected boolean validateNotSameServer(List<GlusterBrickEntity> newBricks, GlusterVolumeEntity glusterVolume, int newReplicaCount) { if (newReplicaCount <= 1) { // no validation required for non-replicated volume types return true; } if (glusterVolume.getReplicaCount() == newReplicaCount) { return validateNotSameServer(newBricks, newReplicaCount); } List<Guid> existingServerList = glusterVolume.getBricks().stream().map(GlusterBrickEntity::getServerId).collect(Collectors.toList()); Set<Guid> brickServers = new HashSet<>(); int incCount = newReplicaCount - glusterVolume.getReplicaCount(); for (int i = 0, j = 0; i <= existingServerList.size() - glusterVolume.getReplicaCount(); i += glusterVolume.getReplicaCount(), j += incCount) { brickServers.addAll(existingServerList.subList(i, i + glusterVolume.getReplicaCount())); List<Guid> subVolNewServers = newBricks.subList(j, j + incCount) .stream() .map(GlusterBrickEntity::getServerId) .collect(Collectors.toList()); for (Guid serverId : subVolNewServers) { if (brickServers.contains(serverId)) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_REPLICASET_SAME_SERVER); return false; } } brickServers.clear(); } return true; } protected boolean validateNotSameServer(List<GlusterBrickEntity> newBricks, int replicaCount) { if (replicaCount <= 1) { // no validation required for non-replicated volume types return true; } for (int count = 0; count <= newBricks.size() - replicaCount; count += replicaCount) { Set<Guid> brickServers = newBricks.subList(count, count + replicaCount) .stream() .map(GlusterBrickEntity::getServerId) .collect(Collectors.toSet()); if (brickServers.size() < replicaCount) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_REPLICASET_SAME_SERVER); return false; } } return true; } }