/**
* Copyright 2008 - CommonCrawl Foundation
*
* 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 org.commoncrawl.service.queryserver.master;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.commoncrawl.async.Timer;
import org.commoncrawl.crawl.common.internal.CrawlEnvironment;
import org.commoncrawl.rpc.base.internal.AsyncClientChannel;
import org.commoncrawl.rpc.base.internal.AsyncRequest;
import org.commoncrawl.rpc.base.internal.NullMessage;
import org.commoncrawl.rpc.base.internal.AsyncRequest.Callback;
import org.commoncrawl.rpc.base.internal.AsyncRequest.Status;
import org.commoncrawl.service.queryserver.BaseConfig;
import org.commoncrawl.service.queryserver.QueryServerSlave;
import org.commoncrawl.service.queryserver.SlaveStatus;
import org.commoncrawl.util.CCStringUtils;
///////////////////////////////////////////////////////
/* Online Crawler State Object */
///////////////////////////////////////////////////////
/** helper object used to encapsulate an online crawler's state information
*
* @author rana
*/
public class QueryServerSlaveState implements AsyncClientChannel.ConnectionCallback {
private static final int HEARTBEAT_TIMER_INTERVAL = 50;
private static final Log LOG = LogFactory.getLog(QueryServerSlaveState.class);
private String _hostName;
private InetSocketAddress _hostAddress;
private long _lastUpdateTime = -1;
private MasterServer _master;
private Timer _heartbeatTimer = null;
private boolean _ignoreHeartbeats = false;
private boolean _online = false;
private AsyncClientChannel _channel;
private QueryServerSlave.AsyncStub _slaveService;
public QueryServerSlaveState(MasterServer master,String hostName){
_master = master;
_hostName = hostName;
InetAddress slaveAddress = null;
try {
LOG.info("Resolving Slave Address for Slave:" + hostName);
slaveAddress = InetAddress.getByName(hostName);
LOG.info("Resolving Slave Address for Slave:" + hostName + " to:" + slaveAddress.getHostAddress());
} catch (UnknownHostException e) {
LOG.error("Unable to Resolve Slave HostName:" + hostName + " Exception:" + CCStringUtils.stringifyException(e));
throw new RuntimeException("Unable to Resolve Slave HostName:" + hostName + " Exception:" + CCStringUtils.stringifyException(e));
}
_hostAddress = new InetSocketAddress(slaveAddress.getHostAddress(),CrawlEnvironment.DEFAULT_QUERY_SLAVE_RPC_PORT);
if (_hostAddress == null) {
throw new RuntimeException("Invalid HostName String in Query Slave Registration: " + _hostName);
}
else {
LOG.info("Host Address for Slave:" + hostName +" is:" + _hostAddress);
}
}
public void connect() throws IOException {
//LOG.info("Opening Channel to Host:" + _hostName);
// initialize channel ...
_channel = new AsyncClientChannel(_master.getEventLoop(),_master.getServerAddress(),_hostAddress,this);
_channel.open();
_slaveService = new QueryServerSlave.AsyncStub(_channel);
}
public String getHostName() { return _hostName; }
public int getPort() { return CrawlEnvironment.DEFAULT_QUERY_SLAVE_RPC_PORT; }
public String getFullyQualifiedName() { return getHostName() + ":" + getPort(); }
public long getLastUpdateTime() { return _lastUpdateTime; }
public boolean isOnline() { return _online; }
public QueryServerSlave.AsyncStub getRemoteStub() { return _slaveService; }
void enableHeartbeats() { _ignoreHeartbeats = false; }
void disableHeartbeats() { _ignoreHeartbeats = true; }
boolean areHeartbeatsDisabled() { return _ignoreHeartbeats; }
public void OutgoingChannelConnected(AsyncClientChannel channel) {
LOG.info("Connected to Query Slave:" + _hostName);
slaveOnline();
}
public boolean OutgoingChannelDisconnected(AsyncClientChannel channel) {
LOG.info("Disconnect detected for Slave : "+ _hostName);
slaveOffline();
return false;
}
private void slaveOnline() {
try {
// initialize the slave ...
_slaveService.initialize(_master.getBaseConfigForSlave(this), new Callback<BaseConfig,SlaveStatus> () {
@Override
public void requestComplete(AsyncRequest<BaseConfig, SlaveStatus> request) {
if (request.getStatus() != Status.Success) {
LOG.error("resetState failed on Slave:" + getFullyQualifiedName());
slaveOffline();
}
else {
_online = true;
// notify master of status change ...
updateSlaveStatus(request.getOutput());
// start the heartbeat timer ...
startHeartbeatTimer();
}
}
});
}
catch (IOException e) {
LOG.error(CCStringUtils.stringifyException(e));
slaveOffline();
}
}
private void slaveOffline() {
_online = false;
// kill heartbeats...
killHeartbeatTimer();
// inform master ...
_master.slaveStatusChanged(this,null);
// reconnect channel
if (_channel != null) {
try {
_channel.reconnect();
} catch (IOException e) {
LOG.error(CCStringUtils.stringifyException(e));
}
}
}
private void updateSlaveStatus(SlaveStatus status) {
_lastUpdateTime = System.currentTimeMillis();
// and inform master ...
_master.slaveStatusChanged(this,status);
}
private void startHeartbeatTimer() {
_heartbeatTimer = new Timer(HEARTBEAT_TIMER_INTERVAL,false,new Timer.Callback() {
@Override
public void timerFired(final Timer timer) {
// LOG.info("Heartbeat Timer Fired. Seconding heartbeat message to slave:" + getFullyQualifiedName());
try {
_slaveService.heartbeat(new Callback<NullMessage,SlaveStatus>() {
public void requestComplete(AsyncRequest<NullMessage, SlaveStatus> request) {
// LOG.info("Received Heartbeat message Response from Slave:"+ getFullyQualifiedName());
boolean forceDisconnect = false;
if (request.getStatus() == AsyncRequest.Status.Success) {
if (!areHeartbeatsDisabled()) {
if (request.getOutput().getQueryStatus().size() != 0) {
// LOG.info("Received non-zero QueryStatus list from slave:" + QueryServerSlaveState.this.getFullyQualifiedName());
}
// LOG.info("updating SlaveStatus from heartbeat response for Slave:"+ getFullyQualifiedName());
// update slave status ...
updateSlaveStatus(request.getOutput());
}
else {
// LOG.info("heartbeats are disabled. Skipping response for Slave:"+ getFullyQualifiedName());
}
// need to SET timer because we are not in timerFired context anymore
_master.getEventLoop().setTimer(timer);
}
else {
// LOG.error("Heartbeat request to slave: " + getFullyQualifiedName() +" failed with Status: " + request.getStatus().toString());
forceDisconnect = true;
}
if (forceDisconnect) {
slaveOffline();
}
}
});
}
catch (IOException e ){
slaveOffline();
LOG.error(CCStringUtils.stringifyException(e));
}
}
});
_master.getEventLoop().setTimer(_heartbeatTimer);
}
private void killHeartbeatTimer() {
if (_heartbeatTimer != null) {
_master.getEventLoop().cancelTimer(_heartbeatTimer);
_heartbeatTimer = null;
}
}
}