package com.yahoo.dtf.comm;
import java.io.IOException;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;
import java.util.HashMap;
import com.yahoo.dtf.DTFProperties;
import com.yahoo.dtf.actions.Action;
import com.yahoo.dtf.config.Config;
import com.yahoo.dtf.exception.ParseException;
import com.yahoo.dtf.logger.DTFLogger;
/**
* @dtf.feature Tunneling Components
* @dtf.feature.group Component Setup
*
* @dtf.feature.desc
* <p>
* Sometimes there are setups where communication over the normal DTF ports will
* not be possible because there are firewalls between two points you're trying
* to connect the various components of DTF. These situations are common and
* because of that DTF can tunnel all communication over any other local port
* to talk to the machine in question as long as you setup the tunneling ports
* correctly. Now lets first look at the scenario where we use the tunneling
* feature to tunnel over ssh to a machine that only has ssh open and wouldn't
* allow you to start up an agent and connect to it on whatever random port it
* usually gets assigned. So we have machine A that resides on a network where
* the only way to get to that machine is over ssh and we have machine B where
* we're going to run our DTFC and DTFX. Now we push out the build as we would
* normally do and then on machine B we'd use the tool ssh_tunnel.sh found in
* the distribution base directory. This tool is pretty straightforward and
* requires some Unix commands in order to work at the moment. So here are the
* things we need to decide at this point:
* <ol>
* <li> Whats the port we're going to use on both the DTFA side for listening
* and the DTFC side for forwarding. We'll keep it to the same port for
* simplicity of making sense of the setup, but we need to figure out the
* port before we start anything.
* </li>
* <li> Add a tunnel on the DTFC side using the ssh_tunnel.sh like so
* (remember to always qualify the hostname completely hostname + domain):
* <pre>./ssh_tunnel.sh add B.domain 30000</pre>
* </li>
* <li> Once the above is done you'll have 1 reverse tunnel from machine B to
* machine A on port 2000 that would connect the DTFA to the DTFC and
* you'll also have a local ssh tunnel on port 30000 to port 30000 of the i
* machine B over the ssh tunnel. So now you can start your DTFA on
* machine B like so:
* <pre>./ant.sh run_dtfa -Ddtf.tunneled=true -Ddtf.listen.port=30000</pre>
* (remember to match the port with the right port you assigned during the
* creation of the tunnel on the DTFC as well as to have the dtf.tunneled
* flag set to true otherwise the Agent will not connect tot he DTFC)
* </li>
* <li> You can list the tunnel at anytime by using the:
* <pre>./ssh_tunnel.sh list</pre>
* </li>
* <li> Removing the tunnel is as easy as doing:
* <pre>./ssh_tunnel.sh del B.domain </pre>
* (Again remember to fully qualify the name of the host with the domain
* name in order for this to work correctly), by removing the tunnel.conf
* file you'll remove all the tunnels. Whenever an entry from this file
* disappears the actual ssh tunnels that are running in the background
* will automatically die off on their own within a few seconds.
* </li>
* <li> Now you can run your DTFX any machine you'd like because in this
* scenario we always tunnel all requests through the DTFC. So as long as
* your DTFX and DTFC can communicate then you're tests will execute
* normally.
* </li>
* <li> Other tunneling methods can be used and you can also edit the
* tunnel.conf file to redirect traffic through a TCPReflector like tool
* and be able to debug the data being sent.
* </li>
* </ol>
*
*/
public class RMITunnelSF extends RMISocketFactory
implements Serializable {
private static final long serialVersionUID = 1L;
private static DTFLogger _logger = DTFLogger.getLogger(RMITunnelSF.class);
public RMITunnelSF() { }
@Override
public ServerSocket createServerSocket(int port) throws IOException {
return new ServerSocket(port);
}
public Socket createSocket(String host, int port) throws IOException {
HashMap<String, Integer> tunnels = Comm.getTunnels();
Config config = Action.getConfig();
boolean tunneled = false;
try {
tunneled =
config.getPropertyAsBoolean(DTFProperties.DTF_TUNNELED, false);
} catch (ParseException e) {
throw new IOException("Error parsing dtf.tunneled value.");
}
String key = host + port;
if ( tunneled || tunnels.containsKey(key) ) {
if (tunnels.get(key) != null)
port = tunnels.get(key);
host = "127.0.0.1";
if (_logger.isDebugEnabled())
_logger.debug("Tunneling through [" + host + ":" + port + "]");
} else {
if (_logger.isDebugEnabled())
_logger.debug("No tunnels matching [" + host + ":" + port + "]");
}
return new Socket(host,port);
}
/*
* This avoids re-creating sockets when there is one already available
*/
public boolean equals(Object that) {
return that != null && that.getClass() == this.getClass();
}
@Override
public int hashCode() {
return super.hashCode();
}
}