package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.aaa.SessionDataContainer;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.utils.CorrelationIdTracker;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrevalidatingMultipleActionsRunner implements MultipleActionsRunner {
private static final Logger log = LoggerFactory.getLogger(PrevalidatingMultipleActionsRunner.class);
private static final int CONCURRENT_ACTIONS = 10;
private VdcActionType actionType = VdcActionType.Unknown;
private final Set<VdcActionParametersBase> parameters;
private final ArrayList<CommandBase<?>> commands = new ArrayList<>();
protected boolean isInternal;
private boolean isWaitForResult = false;
@Inject
private SessionDataContainer sessionDataContainer;
@Inject
NestedCommandFactory commandFactory;
/**
* Execute the actions only if Validate of all the requests returns true
*/
protected boolean isRunOnlyIfAllValidationPass = false;
protected CommandContext commandContext;
public PrevalidatingMultipleActionsRunner(VdcActionType actionType,
List<VdcActionParametersBase> parameters,
CommandContext commandContext,
boolean isInternal) {
this.actionType = actionType;
this.isInternal = isInternal;
this.commandContext = commandContext;
this.parameters = new LinkedHashSet<>(parameters);
}
protected Set<VdcActionParametersBase> getParameters() {
return parameters;
}
protected ArrayList<CommandBase<?>> getCommands() {
return commands;
}
@Override
public ArrayList<VdcReturnValueBase> execute() {
// sanity - don't do anything if no parameters passed
if (parameters == null || parameters.isEmpty()) {
log.info("{} of type '{}' invoked with no actions", this.getClass().getSimpleName(), actionType);
return new ArrayList<>();
}
ArrayList<VdcReturnValueBase> returnValues = new ArrayList<>();
try {
initCommandsAndReturnValues(returnValues);
invokeCommands(returnValues);
} catch (RuntimeException e) {
log.error("Failed to execute multiple actions of type '{}': {}", actionType, e.getMessage());
log.error("Exception", e);
}
return returnValues;
}
private void initCommandsAndReturnValues(ArrayList<VdcReturnValueBase> returnValues) {
VdcReturnValueBase returnValue;
for (VdcActionParametersBase parameter : getParameters()) {
parameter.setMultipleAction(true);
returnValue = ExecutionHandler.evaluateCorrelationId(parameter);
if (returnValue == null) {
getCommands()
.add(commandFactory.createWrappedCommand(commandContext, actionType, parameter, isInternal));
} else {
returnValues.add(returnValue);
}
}
}
private void invokeCommands(ArrayList<VdcReturnValueBase> returnValues) {
if (canRunActions(returnValues)) {
if (isWaitForResult) {
invokeSyncCommands();
} else {
invokeCommands();
}
} else if (isRunOnlyIfAllValidationPass) {
freeLockForValidationPassedCommands();
}
}
private boolean canRunActions(ArrayList<VdcReturnValueBase> returnValues) {
if (getCommands().size() == 1) {
CorrelationIdTracker.setCorrelationId(getCommands().get(0).getCorrelationId());
returnValues.add(getCommands().get(0).validateOnly());
} else {
checkValidatesAsynchronously(returnValues);
}
if (isRunOnlyIfAllValidationPass) {
for (VdcReturnValueBase value : returnValues) {
if (!value.isValid()) {
return false;
}
}
}
return true;
}
private void freeLockForValidationPassedCommands() {
getCommands().stream().filter(command->command.getReturnValue().isValid()).forEach(command->command.freeLock());
}
/**
* Check Validates of all commands. We perform checks for all commands at
* the same time the number of threads is managed by java
*/
private void checkValidatesAsynchronously(
ArrayList<VdcReturnValueBase> returnValues) {
for (int i = 0; i < getCommands().size(); i += CONCURRENT_ACTIONS) {
int handleSize = Math.min(CONCURRENT_ACTIONS, getCommands().size() - i);
int fixedSize = i + handleSize;
List<Callable<VdcReturnValueBase>> validateTasks = new ArrayList<>();
for (int j = i; j < fixedSize; j++) {
validateTasks.add(buildValidateAsynchronously(j, fixedSize));
}
returnValues.addAll(ThreadPoolUtil.invokeAll(validateTasks));
}
}
private Callable<VdcReturnValueBase> buildValidateAsynchronously(
final int currentValidateId, final int totalSize) {
return () -> runValidateOnly(currentValidateId, totalSize);
}
protected VdcReturnValueBase runValidateOnly(final int currentValidateId, final int totalSize) {
CommandBase<?> command = getCommands().get(currentValidateId);
String actionType = command.getActionType().toString();
CorrelationIdTracker.setCorrelationId(command.getCorrelationId());
try {
log.info("Start running Validate for command number {}/{} (Command type '{}')",
currentValidateId + 1,
totalSize,
actionType);
return command.validateOnly();
} finally {
log.info("Finish handling Validate for command number {}/{} (Command type '{}')",
currentValidateId + 1,
totalSize,
actionType);
}
}
protected void runCommands() {
for (CommandBase<?> command : getCommands()) {
if (command.getReturnValue().isValid()) {
executeValidatedCommand(command);
}
}
}
protected void invokeCommands() {
ThreadPoolUtil.execute(() -> runCommands());
}
protected void invokeSyncCommands() {
runCommands();
}
/**
* Executes commands which passed validation and creates monitoring objects.
*
* @param command
* The command to execute
*/
protected void executeValidatedCommand(CommandBase<?> command) {
if (!isInternal) {
logExecution(log,
sessionDataContainer,
command.getParameters().getSessionId(),
String.format("command %s", actionType));
}
commandFactory.prepareCommandForMonitoring(commandContext, command);
command.executeAction();
}
@Override
public void setIsRunOnlyIfAllValidatePass(boolean isRunOnlyIfAllValidationPass) {
this.isRunOnlyIfAllValidationPass = isRunOnlyIfAllValidationPass;
}
@Override
public void setIsWaitForResult(boolean waitForResult) {
this.isWaitForResult = waitForResult;
}
}