package codeine.servlets.command_backup;
import java.io.File;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.eclipse.jetty.http.HttpStatus;
import codeine.SnoozeKeeper;
import codeine.configuration.IConfigurationManager;
import codeine.configuration.PathHelper;
import codeine.credentials.CredHelper;
import codeine.jsons.auth.EncryptionUtils;
import codeine.jsons.command.CommandInfo;
import codeine.jsons.command.CommandInfoForSpecificNode;
import codeine.jsons.command.CommandParameterInfo;
import codeine.jsons.global.ExperimentalConfJsonStore;
import codeine.jsons.peer_status.PeerStatus;
import codeine.jsons.project.ProjectJson;
import codeine.model.Constants;
import codeine.model.ExitStatus;
import codeine.model.Result;
import codeine.servlet.AbstractServlet;
import codeine.servlets.command_backup.ProcessExecuterBackup.ProcessExecuterBuilderBackup;
import codeine.utils.StringUtils;
import codeine.utils.os.OperatingSystem;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class CommandNodeServletBackup extends AbstractServlet
{
private static final Logger log = Logger.getLogger(CommandNodeServletBackup.class);
private static final long serialVersionUID = 1L;
@Inject private PathHelper pathHelper;
@Inject private IConfigurationManager configurationManager;
@Inject private ExperimentalConfJsonStore experimentalConfJsonStore;
@Inject private SnoozeKeeper snoozeKeeper;
@Inject private PeerStatus projectStatusUpdater;
@Override
public void myPost(HttpServletRequest request, HttpServletResponse res) {
log.info("start handle command");
if (Boolean.parseBoolean(getParameter(request, Constants.UrlParameters.FORCE)) || experimentalConfJsonStore.get().allow_concurrent_commands_in_peer()) {
executeCommandNotSync(request, res);
}
else {
executeCommandSync(request, res);
}
log.info("finished handle command");
}
/**
* this prevents multiple commands on the same peer, so preventing upgrade the peer during command for example
*/
private void executeCommandNotSync(HttpServletRequest request, HttpServletResponse res) {
executeInternal(request, res);
}
private synchronized void executeCommandSync(HttpServletRequest request, HttpServletResponse res) {
executeInternal(request, res);
}
private void executeInternal(HttpServletRequest request, HttpServletResponse res) {
ShellScriptBackup cmdScript = null;
snoozeKeeper.snoozeAll();
final PrintWriter writer = getWriter(res);
try {
String parameter = Constants.UrlParameters.DATA_NAME;
String data = getParameter(request, parameter);
CommandInfo commandInfo = gson().fromJson(data, CommandInfo.class);
String data2 = getParameter(request, Constants.UrlParameters.DATA_ADDITIONAL_COMMAND_INFO_NAME);
CommandInfoForSpecificNode commandInfo2 = gson().fromJson(data2, CommandInfoForSpecificNode.class);
if (null != commandInfo2.key()) {
String decrypt = EncryptionUtils.decrypt(Constants.CODEINE_API_TOKEN_DERIVER, commandInfo2.key());
validateKey(decrypt);
}
else {
log.warn("key is null", new RuntimeException());
}
// writer.println("INFO: Executing on node " + commandInfo2.node_alias());
String dir = pathHelper.getCommandsDir(commandInfo.project_name());
String script_content = commandInfo.script_content();
String file = dir + File.separator + commandInfo.command_name();
ProjectJson project = getProject(commandInfo.project_name());
boolean windows_peer = project.operating_system() == OperatingSystem.Windows;
if (null != script_content){
//new
cmdScript = new ShellScriptBackup(file, script_content, windows_peer, commandInfo2.tmp_dir());
file = cmdScript.create();
}
else {
log.info("command not found " + file);
writer.println("command not found " + file);
res.setStatus(HttpStatus.NOT_FOUND_404);
return;
}
List<String> cmd = Lists.newArrayList();
List<String> cmdForOutput = Lists.newArrayList();
String cred = commandInfo.cred();
log.info("credentials: " + cred);
if (!StringUtils.isEmpty(cred) && !windows_peer){
writer.println("credentials = " + cred);
cmd.add(PathHelper.getReadLogs());
cmd.add(encodeIfNeeded(cred, cred));
}
if (windows_peer) {
cmd.add(encodeIfNeeded("cmd", cred));
cmd.add(encodeIfNeeded("/c", cred));
cmd.add(encodeIfNeeded("call", cred));
}
else {
cmd.add(encodeIfNeeded("/bin/sh", cred));
cmd.add(encodeIfNeeded("-xe", cred));
}
cmd.add(encodeIfNeeded(file, cred));
if (windows_peer) {
cmdForOutput.add("cmd");
cmdForOutput.add("/c");
cmdForOutput.add("call");
}
else {
cmdForOutput.add("/bin/sh");
cmdForOutput.add("-xe");
}
cmdForOutput.add(file);
writer.println("$ " + StringUtils.collectionToString(cmdForOutput));
Function<String, Void> function = new Function<String, Void>(){
@Override
public Void apply(String input){
writer.println(input);
writer.flush();
return null;
}
};
Map<String, String> env = getEnvParams(commandInfo);
env.put(Constants.EXECUTION_ENV_PROJECT_NAME, commandInfo.project_name());
env.put(Constants.EXECUTION_ENV_NODE_NAME, commandInfo2.node_name());
env.put(Constants.EXECUTION_ENV_NODE_ALIAS, commandInfo2.node_alias());
env.put(Constants.EXECUTION_ENV_NODE_TAGS, StringUtils.collectionToString(projectStatusUpdater.getTags(commandInfo.project_name(), commandInfo2.node_name()), ";"));
Result result = new ProcessExecuterBuilderBackup(cmd, pathHelper.getProjectDir(commandInfo.project_name())).cmdForOutput(cmdForOutput).timeoutInMinutes(commandInfo.timeoutInMinutes()).function(function).env(env).build().execute();
writer.println(Constants.COMMAND_RESULT + result.exit());
writer.flush();
log.info("command exit status is " + result.exit());
} catch (Exception ex) {
try {
log.warn("failed on command execution", ex);
writer.println(Constants.COMMAND_RESULT + ExitStatus.EXCEPTION);
} catch (Exception e) {
log.warn("failed on command execution2", ex);
}
}
finally {
if (null != cmdScript){
cmdScript.delete();
}
}
}
private void validateKey(String decrypt) {
List<String> l = Splitter.on("#").splitToList(decrypt);
if (l.size() != 2){
log.warn("format error");
return;
}
try {
UUID.fromString(l.get(0));
} catch (Exception e) {
log.warn("format error bad parameter(0) " + l.get(0), e);
}
try {
long currentTimeMillis = System.currentTimeMillis();
long serverTime = Long.valueOf(l.get(1));
if (Math.abs(currentTimeMillis - serverTime) > TimeUnit.MINUTES.toMillis(1)) {
log.warn("time from server does not match");
}
} catch (NumberFormatException e) {
log.warn("format error bad parameter(1) " + l.get(1), e);
}
}
private Map<String, String> getEnvParams(CommandInfo commandJson) {
Map<String, String> $ = Maps.newHashMap();
for (CommandParameterInfo p : commandJson.parameters()) {
$.put(p.name(), p.value());
}
return $;
}
private String encodeIfNeeded(String text, String credentials) {
return null == credentials ? text: CredHelper.encode(text);
}
private ProjectJson getProject(String projectName) {
return configurationManager.getProjectForName(projectName);
}
@Override
protected boolean checkPermissions(HttpServletRequest request) {
return true;
}
}