/*******************************************************************************
* 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.coordinator;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import org.apache.log4j.Logger;
import staticContent.evaluation.testbed.deploy.discovery.DiscoveryHelper;
import staticContent.evaluation.testbed.deploy.process.ProcessFactory;
import staticContent.evaluation.testbed.deploy.process.ProcessInfo;
import staticContent.evaluation.testbed.deploy.testnode.ITestNode;
import staticContent.evaluation.testbed.deploy.utility.ConfigManager;
import staticContent.evaluation.testbed.deploy.utility.ConfigManager.Type;
import com.healthmarketscience.rmiio.GZIPRemoteInputStream;
import com.healthmarketscience.rmiio.RemoteInputStream;
import com.healthmarketscience.rmiio.RemoteInputStreamClient;
import com.healthmarketscience.rmiio.RemoteInputStreamServer;
public class Coordinator implements ICoordinator {
/**
* Cached testnodes
*/
private Map<String, ITestNode> testnodes;
private DiscoveryHelper discHelper;
private ConfigManager config;
private Logger logger = Logger.getLogger(this.getClass());
private int vpidCounter = 0;
private Map<ITestNode,Set<Integer>> startedProcesses;
public enum DiscoveryMode {
MULTICAST, PRECONFIGURED, REGISTRY
}
/**
* Do not use it directly. Use the getRegistry method.
*/
private Registry registry;
protected static Coordinator instance = null;
/**
* Constructs a new Coordinator instance.
*
*/
protected Coordinator() {
discHelper = new DiscoveryHelper();
testnodes = new HashMap<String, ITestNode>();
config = ConfigManager.getInstance(Type.COORDINATOR);
startedProcesses = new ConcurrentHashMap<ITestNode,Set<Integer>>();
}
public static Coordinator getInstance() {
if (instance == null) {
instance = new Coordinator();
}
return instance;
}
private String getNextVPID(){
synchronized (this) {
vpidCounter++;
return vpidCounter + "";
}
}
/**
* Returns a reference of the registry.
*
* @return a registry instance
*
* @throws RemoteException - if no connection to the registry couldn't be established
*/
public Registry getRegistry() throws RemoteException {
if (registry == null) {
registry = LocateRegistry.getRegistry(config.getString("registryAddress"), config.getInt("registryPort"), new SslRMIClientSocketFactory());
}
else {
// check if there is a connection
try {
// TODO: Ist das nicht overkill?
registry.list();
} catch (Exception e) {
registry = LocateRegistry.getRegistry(config.getString("registryAddress"), config.getInt("registryPort"), new SslRMIClientSocketFactory());
}
}
return registry;
}
/**
* Returns a testnode instance with the given name.
*
* @param testNodeName - name of the wanted testnode
*
* @return a testnode instance
*
* @throws RemoteException - if remote communication with the registry failed
*/
public ITestNode getTestnode(String testNodeName) throws RemoteException {
Registry registryReference = getRegistry();
try {
return (ITestNode)registryReference.lookup(testNodeName);
} catch (NotBoundException e) {
logger.debug("Testnode "+testNodeName+" is not registered. Trigger registration again.");
// TODO Trigger node registration again
logger.debug("Trigger testnode currently not implemented.");
return getTestnode(testNodeName);
}
}
/**
* Returns a set of available testnode names. The available testnodes are
* determined by a discovery process. With the parameter mode you can specify
* in whitch way the discovery process is executed.
*
* @param mode a mode for discovery of the testnodes<br><br>
*
* MULTICAST - testnodes are determined by multicast discovery (works only in a local subnet)<br>
* PRECONFIGURED - testnodes are determined by a preconfigured list of node addresses in the config file<br>
* REGISTRY - testnodes are determined by asking the registry which testnodes have registered<br>
*
* @return a set of testnode names
*/
public Set<String> getAvailableTestnodeNames(DiscoveryMode mode) {
Set<String> discoveredNodes = new HashSet<String>();
try {
switch(mode) {
case REGISTRY:
discoveredNodes = discHelper.getAvailableNodesFromRegistry();
break;
case MULTICAST:
discoveredNodes = discHelper.getAvailableNodesViaMulticast();
break;
case PRECONFIGURED:
List<String> preconfiguredNodes = Arrays.asList(config.getStringArray("testNodeAddress"));
discoveredNodes = discHelper.getAvailableNodesWithPreconfiguredList(preconfiguredNodes);
break;
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return discoveredNodes;
}
/**
* Returns a set of available testnode instances. The available testnodes are
* determined by a discovery process. With the parameter mode you can specify
* in which way the discovery process is executed.
*
* @param mode a mode for discovery of the testnodes<br><br>
*
* MULTICAST - testnodes are determined by multicast discovery (works only in a local subnet)<br>
* PRECONFIGURED - testnodes are determined by a preconfigured list of node addresses in the config file<br>
* REGISTRY - testnodes are determined by asking the registry which testnodes have registered<br>
*
* @return a set of testnode instances
* @throws RemoteException
*/
public Set<ITestNode> getAvailableTestnodes(DiscoveryMode mode) throws RemoteException {
Set<ITestNode> result = new HashSet<ITestNode>();
Registry reg = getRegistry();
synchronized (this) {
testnodes.clear();
for (String testnodeName: getAvailableTestnodeNames(mode)) {
// TODO parallelize this with threads
try {
ITestNode tn = (ITestNode) reg.lookup(testnodeName);
result.add(tn);
testnodes.put(testnodeName, tn);
if (!startedProcesses.containsKey(tn) || startedProcesses.get(tn) == null) {
startedProcesses.put(tn, new HashSet<Integer>());
}
} catch (NotBoundException | RemoteException e) {
logger.error("Could not find remote Testnode object in registry " + registry + "for name " + testnodeName + "!", e);
// Could not find the testnode -> try to unbind
unbindTestNode(testnodeName);
}
}
}
return result;
}
/**
* Returns a set of available testnode instances. The available testnodes are
* determined by asking the registry which testnodes have registered.
*
* @return a set of testnode instances
* @throws RemoteException
*/
public Set<ITestNode> getAvailableTestnodes() throws RemoteException {
Set<ITestNode> result = new HashSet<ITestNode>();
Registry reg = getRegistry();
synchronized (this) {
testnodes.clear();
for (String testnodeName: reg.list()) {
// only testnodes
if(!testnodeName.startsWith("testnode_"))
continue;
// TODO parallelize this with threads
try {
ITestNode tn = (ITestNode) reg.lookup(testnodeName);
result.add(tn);
testnodes.put(testnodeName, tn);
if (!startedProcesses.containsKey(tn) || startedProcesses.get(tn) == null) {
startedProcesses.put(tn, new HashSet<Integer>());
}
} catch (Exception e) {
logger.error("Could not find remote Testnode object in registry " + registry + "for name " + testnodeName + "!", e);
// Could not find the testnode -> try to unbind
unbindTestNode(testnodeName);
}
}
}
return result;
}
/**
* Tries to contact Testnodes via the given mode and orders them to register with the registry.
*
* @param mode
*/
public void discoverTestnodes(DiscoveryMode mode)
{
try {
switch(mode) {
case REGISTRY:
// nodes are already in registry -> nothing to do
break;
case MULTICAST:
// TODO: Die Methode braucht eigentlich keine Rückgabe. Danach können die Testnodes aus der Registry geholt werden.
discHelper.getAvailableNodesViaMulticast();
break;
case PRECONFIGURED:
List<String> preconfiguredNodes = Arrays.asList(config.getStringArray("testNodeAddress"));
// TODO: Die Methode braucht eigentlich keine Rückgabe. Danach können die Testnodes aus der Registry geholt werden.
discHelper.getAvailableNodesWithPreconfiguredList(preconfiguredNodes);
break;
}
} catch (Exception e) {
logger.error("Discovery of testnodes failed in mode "+mode+"!", e);
}
}
/**
* Copies the given file to the testnode with the given name.
*
* @param testNodeName name of the testnode
* @param f file to copy
* @throws NotBoundException
* @throws FileNotFoundException
* @throws IOException
*/
public void InstallOnTestNode(String testNodeName, File f) throws NotBoundException, FileNotFoundException, IOException {
ITestNode testNode = getTestnode(testNodeName);
RemoteInputStreamServer istream = null;
try {
istream = new GZIPRemoteInputStream(new BufferedInputStream(
new FileInputStream(f)));
// call server (note export() call to get actual remote interface)
testNode.installFile(f.getName(), istream.export());
} finally {
// since the server should have consumed the stream in the
// sendFile()
// call, we always want to close the stream
if (istream != null)
istream.close();
}
}
/**
* Copies the given file to the testnode with the given name.
*
* @param testNodeName name of the testnode
* @param f file to copy
* @throws NotBoundException
* @throws FileNotFoundException
* @throws IOException
*/
public void InstallZipOnTestNode(String testNodeName, File f) throws NotBoundException, FileNotFoundException, IOException {
ITestNode testNode = getTestnode(testNodeName);
RemoteInputStreamServer istream = null;
try {
istream = new GZIPRemoteInputStream(new BufferedInputStream(
new FileInputStream(f)));
// call server (note export() call to get actual remote interface)
testNode.installZipFile(f.getName(), istream.export());
} finally {
// since the server should have consumed the stream in the
// sendFile()
// call, we always want to close the stream
if (istream != null)
istream.close();
}
}
/**
* Copies the given file to the testnode with the given name.
*
* @param testNodeName name of the testnode
* @param f file to copy
* @throws NotBoundException
* @throws FileNotFoundException
* @throws IOException
*/
public void InstallZipOnTestNode(ITestNode testNode, File f) throws FileNotFoundException, IOException {
RemoteInputStreamServer istream = null;
try {
istream = new GZIPRemoteInputStream(new BufferedInputStream(
new FileInputStream(f)));
// call server (note export() call to get actual remote interface)
testNode.installZipFile(f.getName(), istream.export());
} finally {
// since the server should have consumed the stream in the
// sendFile()
// call, we always want to close the stream
if (istream != null)
istream.close();
}
}
/**
* Copies the given file to the given testnode.
*
* @param testNode reference of the testnode
* @param f file to copy
* @throws FileNotFoundException
* @throws IOException
*/
public void InstallOnTestNode(ITestNode testNode, File f) throws FileNotFoundException, IOException {
InstallOnTestNode(testNode, f, f.getName());
}
/**
* Copies the given file to the given testnode.
*
* @param testNode reference of the testnode
* @param f file to copy
* @throws FileNotFoundException
* @throws IOException
*/
public void InstallOnTestNode(ITestNode testNode, File f, String filenameOnTarget) throws FileNotFoundException, IOException {
RemoteInputStreamServer istream = null;
try {
istream = new GZIPRemoteInputStream(new BufferedInputStream(
new FileInputStream(f)));
// call server (note export() call to get actual remote interface)
testNode.installFile(filenameOnTarget, istream.export());
} finally {
// since the server should have consumed the stream in the
// sendFile()
// call, we always want to close the stream
if (istream != null)
istream.close();
}
}
/**
* Executes the class with the given className, that is located in the given classPath
* with the given arguments on the testnode with the given name.
*
* @param testNodeName name of the testnode
* @param classPath class path
* @param className name of the class
* @param args arguments
*
* @throws RemoteException
* @throws NotBoundException
*/
public void executeOnTestNode(String testNodeName, String classPath, String className, Map<String,String> args, Map<String,String> vmArgs, Map<String,String> environmentVariables) throws RemoteException, NotBoundException {
ITestNode testNode = getTestnode(testNodeName);
executeOnTestNode(testNode, classPath, className, args, vmArgs, environmentVariables);
}
/**
* Executes the class with the given className, that is located in the given classPath
* with the given arguments on the given testnode.
*
* @param testNode reference of the testnode
* @param classPath class path
* @param className name of the class
* @param args arguments
*
* @throws RemoteException
*/
public void executeOnTestNode(ITestNode testNode, String classPath, String className, Map<String,String> args, Map<String,String> vmArgs, Map<String,String> environmentVariables) throws RemoteException {
String vpid = getNextVPID();
if (startedProcesses.get(testNode) == null) {
startedProcesses.put(testNode, new HashSet<Integer>());
}
startedProcesses.get(testNode).add(Integer.parseInt(vpid));
testNode.execute(vpid, classPath, className, args, vmArgs, environmentVariables);
}
/**
* Executes the class with the given className, that has the same name than the given file
* with the given arguments on the given testnode.
*
* @param testNode reference of the testnode
* @param file local file pointer
* @param className name of the class
* @param args arguments
*
* @throws RemoteException
*/
public void executeOnTestNode(ITestNode testNode, File file, String className, Map<String,String> args, Map<String,String> vmArgs, Map<String,String> environmentVariables) throws RemoteException {
String vpid = getNextVPID();
if (startedProcesses.get(testNode) == null) {
startedProcesses.put(testNode, new HashSet<Integer>());
}
startedProcesses.get(testNode).add(Integer.parseInt(vpid));
testNode.execute(vpid, file.getName(), className, args, vmArgs, environmentVariables);
}
/**
* Returns a set of process instances that are running on the testnode with the given name.
*
* @param testNodeName - name of a testnode
*
* @return set of processes
*/
public Set<ProcessInfo> getProcesses(String testNodeName) {
Set<ProcessInfo> result = new HashSet<ProcessInfo>();
try {
ITestNode testnode = getTestnode(testNodeName);
String processJsonString = testnode.getRunningProcesses();
result = ProcessFactory.getProcessesFromJson(processJsonString, testNodeName, testnode);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return result;
}
/**
* Returns a set of process instances that are running on the given testnode.
*
* @param testNodeName - reference of the testnode
*
* @return set of processes
*/
public Set<ProcessInfo> getProcesses(ITestNode testnode) {
Set<ProcessInfo> result = new HashSet<ProcessInfo>();
try {
String processJsonString = testnode.getRunningProcesses();
result = ProcessFactory.getProcessesFromJson(processJsonString, testnode.getName(), testnode);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return result;
}
/**
* Returns a set of process instances that are running on all known testnodes.
*
* @return set of processes
*/
public Set<ProcessInfo> getAllProcesses() throws RemoteException {
Set<ProcessInfo> result = new HashSet<ProcessInfo>();
for (String testnodeName : testnodes.keySet()) {
try {
// TODO parallelize this with threads
ITestNode testnode = testnodes.get(testnodeName);
String processJsonString = testnode.getRunningProcesses();
result.addAll(ProcessFactory.getProcessesFromJson(processJsonString, testnodeName, testnode));
} catch (RemoteException re) {
logger.error("Could not access testnode: " + testnodeName, re);
unbindTestNode(testnodeName);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
return result;
}
private void unbindTestNode(String testnodeName)
{
try
{
getRegistry().unbind(testnodeName);
} catch (Exception ex) {
logger.error("Unbinding failed:", ex);
}
}
public String getMessageLog(ITestNode node, String vpid) throws RemoteException, IOException {
String result = null;
InputStream istream = null;
try{
istream = RemoteInputStreamClient.wrap(node.getLogFileStream(vpid));
result = convertStreamToString(istream);
}
catch(Exception e)
{
logger.error("Log File reading failed!",e);
}
finally
{
if(istream != null)
istream.close();
}
return result;
}
private static String convertStreamToString(java.io.InputStream is) {
Scanner s = new java.util.Scanner(is);
s.useDelimiter("\\A");
s.close();
return s.hasNext() ? s.next() : "";
}
public ConfigManager getConfig() {
return config;
}
public void waitForNodeRegistrations(Set<String> expectedNodeNames, Set<String> expectedNodeAddresses) throws InterruptedException, IOException {
Set<String> availableNodeNames = this.getAvailableTestnodeNames(DiscoveryMode.REGISTRY);
// TODO log which node is not registered yet
if (!availableNodeNames.containsAll(expectedNodeNames)) {
logger.debug("Not all expected physical nodes have registered. Wait for registrations.");
triggerNodesToRegister(expectedNodeAddresses);
Thread.sleep(1000);
waitForNodeRegistrations(expectedNodeNames, expectedNodeAddresses);
}
}
public void triggerNodesToRegister(Collection<String> nodeAddresses) throws IOException {
discHelper.getAvailableNodesWithPreconfiguredList(nodeAddresses);
}
/**
* Returns a set of vpids of started processes of the given testnode.
*
* @param node test node
*
* @return set of vpids of started processes
*/
public Set<Integer> getStartedProcessesOnNode(ITestNode node) {
return startedProcesses.get(node);
}
/**
* Resets internal counters of the coordinator.
*/
public void reset() {
startedProcesses = new ConcurrentHashMap<ITestNode,Set<Integer>>();
}
public boolean copySensorFileFromTestnode(ITestNode testnode, int vpid, String fileName) throws RemoteException {
String sensorDir = System.getProperty("user.dir") +"/inputOutput/testbed/tmp";
RemoteInputStream remoteInputStream = testnode.getLogFileStream(vpid+"");
InputStream fileData = null;
OutputStream os = null;
try {
fileData = RemoteInputStreamClient.wrap(remoteInputStream);
os = new FileOutputStream(sensorDir + "/" + 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;
}
}
return true;
}
public File copyFileFromTestnode(ITestNode testnode, String sourceFileName, String targetFileName) throws RemoteException {
String tmpDir = System.getProperty("user.dir") +"/inputOutput/testbed/tmp";
RemoteInputStream remoteInputStream = testnode.getStreamFromFile(sourceFileName);
InputStream fileData = null;
OutputStream os = null;
try {
fileData = RemoteInputStreamClient.wrap(remoteInputStream);
os = new FileOutputStream(tmpDir + "/" + targetFileName,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 null;
}
finally {
try {
if(fileData != null)
fileData.close();
if(os != null)
os.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
return null;
}
}
return new File(tmpDir+"/"+targetFileName);
}
}