package codeine.command_peer;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.apache.log4j.Logger;
import codeine.api.CommandExecutionStatusInfo;
import codeine.api.NodeGetter;
import codeine.api.NodeInfoNameAndAlias;
import codeine.api.NodeWithPeerInfo;
import codeine.api.ScehudleCommandExecutionInfo;
import codeine.command_peer.CommandFileWriter.CommandFileWriterItem;
import codeine.configuration.Links;
import codeine.configuration.PathHelper;
import codeine.jsons.peer_status.PeerStatusJsonV2;
import codeine.jsons.project.ProjectJson;
import codeine.model.Constants;
import codeine.permissions.IUserWithPermissions;
import codeine.plugins.DiscardOldCommandsPlugin;
import codeine.statistics.IMonitorStatistics;
import codeine.utils.ExceptionUtils;
import codeine.utils.FilesUtils;
import codeine.utils.SocketUtils;
import codeine.utils.StringUtils;
import codeine.utils.TextFileUtils;
import codeine.utils.ThreadUtils;
import com.google.common.base.Function;
import com.google.gson.Gson;
import com.google.inject.Inject;
public class AllNodesCommandExecuter {
private static final Logger log = Logger.getLogger(AllNodesCommandExecuter.class);
@Inject private Links links;
@Inject private PathHelper pathHelper;
@Inject private NodeGetter nodeGetter;
@Inject private IMonitorStatistics monitorsStatistics;
@Inject private DiscardOldCommandsPlugin discardOldCommandsPlugin;
@Inject private CommandFileWriter commandFileWriter;
@Inject private Gson gson;
private int total;
private int count;
private int fail;
private BufferedWriter writer;
private boolean active = true;
private long commandId;
private String dirNameFull;
private ScehudleCommandExecutionInfo commandData;
private CommandExecutionStatusInfo commandExecutionInfo;
private ProjectJson project;
private IUserWithPermissions userObject;
private Object fileWriteSync = new Object();
private CommandExecutionStrategy strategy;
private String cancelingUser;
public long executeOnAllNodes(IUserWithPermissions userObject, ScehudleCommandExecutionInfo commandData, ProjectJson project) {
this.project = project;
this.userObject = userObject;
discardOldCommandsPlugin.queueForDelete(project);
try {
this.commandData = commandData;
this.total = commandData.nodes().size();
commandId = getNewDirName();
dirNameFull = pathHelper.getCommandOutputDir(commandData.command_info().project_name(), String.valueOf(commandId));
FilesUtils.mkdirs(dirNameFull);
String pathname = dirNameFull + "/log";
File file = new File(pathname);
FilesUtils.createNewFile(file);
createCommandDataFile(userObject.user().username());
writer = TextFileUtils.getWriter(file, false);
log.info("running command " + commandData.command_info().command_name() + " with concurrency " + commandData.command_info().concurrency() + "by " + userObject.user());
String nodesWord = commandData.nodes().size() == 1 ? "node" : "nodes";
writeLine("running command '"+commandData.command_info().command_name()+"' on " + commandData.nodes().size() + " " + nodesWord + " by " + userObject.user().username());
writeNodesList(commandData);
updatePeersAddresses();
Thread commandThread = ThreadUtils.createThread(new Runnable() {
@Override
public void run() {
execute();
};
}, "AllNodesCommandExecuter_"+commandData.command_info().command_name());
commandThread.start();
monitorsStatistics.updateCommand(commandExecutionInfo);
return commandId;
} catch (Exception ex) {
finish();
throw ExceptionUtils.asUnchecked(ex);
}
}
private void updatePeersAddresses() {
for (NodeWithPeerInfo n : commandData.nodes()) {
PeerStatusJsonV2 p = nodeGetter.peer(n.peer_key());
if (null == p) {
writeLine("Warning: ignoring node '" + n.alias() + "' since peer not found");
continue;
}
n.peer_address(p.address_port());
}
}
public void writeNodesList(ScehudleCommandExecutionInfo commandData) {
if (commandData.nodes().size() < 11) {
Function<NodeWithPeerInfo, String> predicate = new Function<NodeWithPeerInfo, String>(){
@Override
public String apply(NodeWithPeerInfo input) {
return input.alias();
}
};
writeLine("nodes list: " + StringUtils.collectionToString(commandData.nodes(), predicate));
}
}
private void finish() {
log.info("Finishing command " + commandExecutionInfo.id());
if (null != commandExecutionInfo) {
commandExecutionInfo.finish();
}
try {
updateJson();
FilesUtils.createNewFile(dirNameFull + Constants.COMMAND_FINISH_FILE);
} catch (Exception e) {
log.warn("Failed to mark command as finished " + commandExecutionInfo, e);
}
active = false;
}
private void execute() {
try {
initStrategy();
strategy.execute();
if (strategy.isCancel()) {
writeLine("Execution was canceled by user " + cancelingUser);
}
if (strategy.isError()) {
writeLine(strategy.error());
}
writeFooter();
if (null != commandData.address_to_notify()) {
int status = fail > 0 ? 1 : 0;
String message = "command-finished,project=" + commandData.command_info().project_name() + ",id=" + commandId + ",status=" + status;
log.info("sending finished event: " + message);
SocketUtils.sendToPort(commandData.address_to_notify(), message);
}
} finally {
finish();
}
}
private void initStrategy() {
switch (commandData.command_info().command_strategy())
{
case Single: {
strategy = new SingleNodeCommandStrategy(this, commandData, links,project, userObject);
break;
}
case Immediately: {
strategy = new ImmediatlyCommandStrategy(this, commandData, links,project, userObject);
break;
}
case Progressive: {
strategy = new ProgressiveExecutionStrategy(this, commandData, links, nodeGetter,project, userObject);
break;
}
default:
throw new IllegalStateException("couldnt handle strategy " + commandData.command_info().command_strategy());
}
}
private void writeFooter() {
writeLine("finished!");
Function<NodeInfoNameAndAlias, String> f = new Function<NodeInfoNameAndAlias, String>() {
@Override
public String apply(NodeInfoNameAndAlias n){
return n.alias();
}
};
if (!commandExecutionInfo.fail_list().isEmpty()) {
writeLine("failed nodes: " + StringUtils.collectionToString(commandExecutionInfo.fail_list(), f));
}
writeLine("=========> aggregate-command-statistics (success/total): " + (total - fail) + "/" + total + "\n");
}
private void createCommandDataFile(String user) {
commandExecutionInfo = new CommandExecutionStatusInfo(user, commandData.command_info().command_name(), commandData.command_info().parameters(), commandData.command_info().project_name(),
commandData.nodes(), commandId);
FilesUtils.createNewFile(commandFile());
updateJson();
}
private String commandFile() {
return dirNameFull + Constants.JSON_COMMAND_FILE_NAME;
}
private long getNewDirName() {
long i = 0;
String dir = pathHelper.getAllCommandsInProjectOutputDir(commandData.command_info().project_name());
List<String> filesInDir = FilesUtils.getFilesInDir(dir);
for (String dir1 : filesInDir) {
try {
long j = Long.parseLong(dir1);
i = Math.max(i, j);
} catch (NumberFormatException e) {
log.debug("error parsing " + dir1);
}
}
return i + 1;
}
void writeLine(String line) {
writeLineToFile(line);
}
private synchronized void writeLineToFile(String line) {
try {
writer.append(line);
writer.newLine();
writer.flush();
} catch (IOException e) {
throw ExceptionUtils.asUnchecked(e);
}
}
public void fail(NodeWithPeerInfo node) {
log.debug("node fail " + node.name());
fail++;
commandExecutionInfo.addFailedNode(node);
}
public void nodeSuccess(NodeWithPeerInfo node) {
log.debug("node success " + node.name());
commandExecutionInfo.addSuccessNode(node);
}
public void workerFinished() {
count++;
updateJsonAsync();
}
public boolean isActive() {
return active;
}
private void updateJsonAsync() {
commandFileWriter.queue(new CommandFileWriterItem(fileWriteSync, commandFile(), commandExecutionInfo));
}
private void updateJson() {
String json;
json = gson.toJson(commandExecutionInfo);
synchronized (fileWriteSync) {
TextFileUtils.setContents(commandFile(), json);
}
}
public String name() {
return commandData.command_info().command_name();
}
public int success() {
return (int) (count - fail) * 100 / total;
}
public int error() {
return fail * 100 / total;
}
public String project() {
return commandData.command_info().project_name();
}
public int nodes() {
return commandData.nodes().size();
}
public CommandExecutionStatusInfo commandData() {
return commandExecutionInfo;
}
public long id() {
return commandId;
}
public void cancel(String username) {
strategy.setCancel();
this.cancelingUser = username;
}
public List<NodeWithPeerInfo> nodesList() {
return commandData.nodes();
}
public Object fileWriteSync() {
return fileWriteSync;
}
public CommandExecutionStatusInfo commandExecutionInfo() {
return commandExecutionInfo;
}
public String commandString() {
return commandExecutionInfo().project_name() + "/" + commandExecutionInfo().command_name() + "/" + commandExecutionInfo().id();
}
}