/******************************************************************************* * gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/ * Copyright (C) 2014 SVS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package staticContent.evaluation.testbed.deploy.testnode; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.ProcessBuilder.Redirect; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.exception.ZipException; import org.apache.log4j.Logger; import org.json.JSONObject; import staticContent.evaluation.testbed.deploy.process.ProcessInfo; import staticContent.evaluation.testbed.deploy.process.ProcessUtility; import staticContent.evaluation.testbed.deploy.utility.NetworkUtility; import com.healthmarketscience.rmiio.GZIPRemoteInputStream; import com.healthmarketscience.rmiio.RemoteInputStream; import com.healthmarketscience.rmiio.RemoteInputStreamClient; import com.healthmarketscience.rmiio.RemoteInputStreamServer; public class TestNode extends UnicastRemoteObject implements Serializable, ITestNode { private static final long serialVersionUID = 4572244666093684182L; private static final Logger logger = Logger.getLogger("TestNode"); private final String name; private final String hostname; private static final String projectRootDir = System.getProperty("user.dir"); private static final String testbedRootDir = projectRootDir +"/inputOutput/testbed"; private static final String testbedInstallDir = testbedRootDir +"/install"; private static final String testbedSensorLogDir = testbedRootDir +"/log"; //TODO: zusammenlegen? private final Map<String, Process> managedProcesses = new HashMap<String, Process>(); private final Map<String, ProcessInfo> managedProcessInfos = new HashMap<String, ProcessInfo>(); public TestNode(String name, String hostname) throws RemoteException { super(0, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory(null, null, true)); this.name = name; this.hostname = hostname; } @Override public boolean installFile(String fileName, RemoteInputStream remoteInputStream) { String installDir = ""; InputStream fileData = null; OutputStream os = null; try { installDir = testbedInstallDir; fileData = RemoteInputStreamClient.wrap(remoteInputStream); os = new FileOutputStream(installDir + "/" + fileName, false); byte[] buffer = new byte[4096]; for (int n; (n = fileData.read(buffer)) != -1; ) os.write(buffer, 0, n); } catch (Exception e) { logger.error(e.getMessage(), e); return false; } finally { try { if(fileData != null) fileData.close(); if(os != null) os.close(); } catch (IOException e) { logger.error(e.getMessage(), e); return false; } } logger.info("installed: " + installDir + "/" + fileName); return true; } @Override public File[] getInstalledFiles() { try { File installDir = new File(testbedInstallDir); return installDir.listFiles(); } catch(Exception e) { logger.error("Could not load installed file list!", e); } return new File[0]; }; @Override public boolean execute(String vpid, String classpath, String classname, Map<String,String> args, Map<String,String> vmArgs, Map<String,String> environmentVariables) throws RemoteException { String installDir = ""; try { if(managedProcesses.containsKey(vpid)) throw new InvalidParameterException("VPID already taken on this Testnode!"); args = replacePlaceholders(args); vmArgs = replacePlaceholders(vmArgs); Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String arg0, String arg1) { /* * order is: -noGUI, -TOOL, -CONFIGFILE, -OVERWRITE and the rest */ switch(arg0) { case "-noGUI": return -1; case "-TOOL": if (arg1.equals("-noGUI")) return 1; else return -1; case "-CONFIGFILE": if (arg1.equals("-OVERWRITE")) return -1; else return 1; default: return 1; } } }; Map<String,String> orderedArgs = new TreeMap<String,String>(comparator); orderedArgs.putAll(args); installDir = testbedInstallDir; String logfilePath = testbedSensorLogDir +"/process_"+vpid+".log"; String argString = generateArgString(orderedArgs); String vmArgString = generateArgString(vmArgs); // just for display in the coordinator String programCall = "java "+vmArgString+" -cp \""+installDir+"/"+classpath+"\" \""+classname + "\" '"+argString+"'"; List<String> processCommand = new ArrayList<String>(); if (environmentVariables == null) { environmentVariables = new HashMap<String,String>(); } processCommand.add("java"); if (!vmArgString.isEmpty()) processCommand.add(vmArgString); processCommand.add(((classname.isEmpty())?"-jar":"-cp")); processCommand.add(classpath); if (!classname.isEmpty()) processCommand.add(classname); // add args to command for (Map.Entry<String, String> entry : orderedArgs.entrySet()) { String value = entry.getValue(); processCommand.add(entry.getKey()+((value.isEmpty()) ? "" : "="+value)); } // initialize the ProcessBuilder ProcessBuilder builder = new ProcessBuilder(processCommand); builder.directory(new File(installDir)); builder.environment().putAll(environmentVariables); // create log file if none exists yet File logFile = new File(logfilePath); synchronized (this) { if(!logFile.exists()) logFile.createNewFile(); } // Redirect the output to the log file builder.redirectErrorStream(true); builder.redirectOutput(Redirect.appendTo(logFile)); logger.info("Create process with the following command: "+ builder.command()); // create and store the Process for later termination Process process = builder.start(); if(process == null) throw new Exception("Process start failed!"); managedProcesses.put(vpid, process); managedProcessInfos.put(vpid, new ProcessInfo(vpid, programCall)); } catch (Exception e) { logger.error(e.getMessage(), e); return false; } if(ProcessUtility.isRunning(managedProcesses.get(vpid))) logger.info("Process successfully started."); else logger.info("Process start failed or process already exited!"); return true; } public static String generateArgString(Map<String, String> args) { String result = ""; for (Map.Entry<String, String> entry : args.entrySet()) { String value = entry.getValue(); String arg = entry.getKey()+((value.isEmpty()) ? "" : "="+value); result += ((result.isEmpty()) ? "" : " ")+arg; } return result; } @Override public String getRunningProcesses() throws RemoteException { logger.debug("Find running processes."); try { JSONObject resultObj = new JSONObject(); JSONObject jProcessesObj = new JSONObject(); for(Map.Entry<String,Process> processEntry : managedProcesses.entrySet()) { ProcessInfo info = managedProcessInfos.get(processEntry.getKey()); jProcessesObj.put(info.getVpid(), info.toJson()); } resultObj.putOpt("hostName", getHostName()); resultObj.putOpt("processes", jProcessesObj); return resultObj.toString(); } catch (Exception e) { logger.error(e.getMessage(), e); throw new RemoteException(e.getMessage()); } } @Override public boolean killProcess(String vpid) throws RemoteException { Process process = managedProcesses.get(vpid); try{ managedProcesses.remove(vpid); managedProcessInfos.remove(vpid); process.destroy(); logger.debug("Killed process with vpid "+vpid); return true; } catch(Exception e) { logger.error("Could not kill the process!", e); } logger.debug("Killing process with vpid "+vpid+" failed."); return false; } /** * Replaces each defined placeholder with the placeholder value on the testnode. * Defined placeholders are: * * <projectRoot> - path to the root directory of the project * * @param input * * @return string with replaced placeholders */ protected static String replacePlaceholders(String input) { String result = input; result = result.replaceAll("<projectRoot>", projectRootDir); result = result.replaceAll("<testbedRoot>", testbedRootDir); return result; } protected static Map<String,String> replacePlaceholders(Map<String,String> map) { for(String key: map.keySet()) { map.put(key, replacePlaceholders(map.get(key))); } return map; } @Override public String getName() { return name; } @Override public String getHostName() { return hostname; } @Override public RemoteInputStream getLogFileStream(String vpid) { String logfilePath = null; RemoteInputStreamServer istream = null; try { logfilePath = testbedSensorLogDir +"/process_"+vpid+".log"; istream = new GZIPRemoteInputStream(new BufferedInputStream( new FileInputStream(logfilePath))); return istream.export(); } catch (Exception e) { if(istream != null) istream.close(); logger.error("Could not open Log file: "+logfilePath, e); return null; } } @Override public boolean killAllProcesses() throws RemoteException { boolean success = true; // copy to avoid removing elements while iterating over it Set<String> managedProcessesCopy = new HashSet<>(managedProcesses.keySet()); for (String vpid : managedProcessesCopy) { success = killProcess(vpid) && success; } return true; } @Override public boolean deleteAllLogFiles() throws RemoteException { try { String logDir = testbedSensorLogDir; FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".log"); } }; File[] sensorFiles = new File(logDir).listFiles(filter); for (int i = 0; i < sensorFiles.length; i++) { File file = sensorFiles[i]; file.delete(); logger.debug("Deleted log file: "+file.getAbsolutePath()); } } catch (Exception e) { logger.error("Could not delete all log files: ", e); return false; } return true; } @Override public boolean executeCommand(String[] command, boolean waitFor) throws RemoteException { for (int i = 0; i < command.length; i++) { command[i] = replacePlaceholders(command[i]); } ProcessBuilder builder = new ProcessBuilder(command); logger.debug("Execute command: "+builder.command()); Process process; try { process = builder.start(); if (process == null) return false; } catch (IOException e) { logger.error(e.getMessage(), e); return false; } if (waitFor) { try { process.waitFor(); } catch (InterruptedException e) { logger.error(e.getMessage(), e); } } logger.debug("Executed command: "+builder.command()); return true; } @Override public Set<String> getAssignedVirtualAddresses() throws RemoteException { return NetworkUtility.getVirtualIpAddresses(); } @Override public boolean installZipFile(String fileName, RemoteInputStream remoteInputStream) throws RemoteException { // fetch file boolean success = installFile(fileName, remoteInputStream); if (!success) { return false; } String installDir = testbedInstallDir; String absoluteFilename = installDir+"/"+fileName; // unzip try { ZipFile zipFile = new ZipFile(absoluteFilename); zipFile.extractAll(installDir); } catch (ZipException e) { logger.error("Unable to unzip file: "+absoluteFilename,e); return false; } logger.info("unzipped installed file: " + absoluteFilename + " to dir: " + installDir); return true; } @Override public RemoteInputStream getStreamFromFile(String filename) throws RemoteException { String filePath = replacePlaceholders(filename); RemoteInputStreamServer istream = null; try { istream = new GZIPRemoteInputStream(new BufferedInputStream(new FileInputStream(filePath))); return istream.export(); } catch (Exception e) { if(istream != null) istream.close(); logger.error("Could not open file: "+filePath, e); return null; } } }