package com.linkedin.databus.client.netty; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ import java.io.InputStream; import java.nio.channels.Channels; import java.util.Formatter; import org.apache.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFactory; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.util.Timer; import com.linkedin.databus.client.ChunkedBodyReadableByteChannel; import com.linkedin.databus.client.DatabusBootstrapConnection; import com.linkedin.databus.client.DatabusBootstrapConnectionStateMessage; import com.linkedin.databus.client.netty.AbstractNettyHttpConnection.BaseHttpResponseProcessor; import com.linkedin.databus.client.pub.ServerInfo; import com.linkedin.databus.core.Checkpoint; import com.linkedin.databus.core.DbusClientMode; import com.linkedin.databus.core.DbusConstants; import com.linkedin.databus.core.DbusPrettyLogUtils; import com.linkedin.databus.core.InvalidCheckpointException; import com.linkedin.databus.core.async.ActorMessageQueue; import com.linkedin.databus2.core.container.ExtendedReadTimeoutHandler; import com.linkedin.databus2.core.container.monitoring.mbean.ContainerStatisticsCollector; import com.linkedin.databus2.core.container.request.BootstrapDatabaseTooOldException; import com.linkedin.databus2.core.filter.DbusKeyFilter; public class NettyHttpDatabusBootstrapConnection extends AbstractNettyHttpConnection implements DatabusBootstrapConnection { public static final String MODULE = NettyHttpDatabusBootstrapConnection.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); private static enum State { TARGET_SCN_REQUEST_CONNECT, TARGET_SCN_REQUEST_WRITE, START_SCN_REQUEST_CONNECT, START_SCN_REQUEST_WRITE, STREAM_REQUEST_CONNECT, STREAM_REQUEST_WRITE } private final ActorMessageQueue _callback; private DatabusBootstrapConnectionStateMessage _callbackStateReuse; private ExtendedReadTimeoutHandler _readTimeOutHandler; private State _curState; private Checkpoint _checkpoint; private String _sourcesIdList; private String _sourcesNameList; private int _freeBufferSpace; private final RemoteExceptionHandler _remoteExceptionHandler; private DbusKeyFilter _filter; private GenericHttpResponseHandler _handler; //private MyConnectListener _connectListener; public NettyHttpDatabusBootstrapConnection(ServerInfo server, ActorMessageQueue callback, ClientBootstrap bootstrap, ContainerStatisticsCollector containerStatsCollector, RemoteExceptionHandler remoteExceptionHandler, Timer timeoutTimer, long writeTimeoutMs, long readTimeoutMs, int protocolVersion, ChannelGroup channelGroup) { super(server, bootstrap, containerStatsCollector, timeoutTimer, writeTimeoutMs, readTimeoutMs, channelGroup, protocolVersion, LOG); _callback = callback; _remoteExceptionHandler = remoteExceptionHandler; //_connectListener = new MyConnectListener(); //setConnectListener(_connectListener); } public NettyHttpDatabusBootstrapConnection(ServerInfo relay, ActorMessageQueue callback, ChannelFactory channelFactory, ContainerStatisticsCollector containerStatsCollector, RemoteExceptionHandler remoteExceptionHandler, Timer timeoutTimer, long writeTimeoutMs, long readTimeoutMs, int protocolVersion, ChannelGroup channelGroup) { this(relay, callback, new ClientBootstrap(channelFactory), containerStatsCollector, remoteExceptionHandler, timeoutTimer, writeTimeoutMs, readTimeoutMs, protocolVersion, channelGroup); } @Override public void requestTargetScn(Checkpoint checkpoint, DatabusBootstrapConnectionStateMessage stateReuse) { _checkpoint = checkpoint; _callbackStateReuse = stateReuse; _handler = null; if (!hasConnection()) { connect(State.TARGET_SCN_REQUEST_CONNECT); } else { onTargetScnConnectSuccess(); } } void onTargetScnConnectSuccess() { _curState = State.TARGET_SCN_REQUEST_WRITE; ChannelPipeline channelPipeline = _channel.getPipeline(); _readTimeOutHandler = (ExtendedReadTimeoutHandler)channelPipeline.get(GenericHttpClientPipelineFactory.READ_TIMEOUT_HANDLER_NAME); _readTimeOutHandler.start(channelPipeline.getContext(_readTimeOutHandler)); BootstrapTargetScnHttpResponseProcessor targetResponseProcessor = new BootstrapTargetScnHttpResponseProcessor(this, _callback, _callbackStateReuse, _checkpoint, _remoteExceptionHandler, _readTimeOutHandler); final String url = createTargetScnRequestUrl(); LOG.info("Sending " + url); // Prepare the HTTP request. HttpRequest request = createEmptyRequest(url); sendRequest(request, new TargetScnRequestResultListener(), targetResponseProcessor); } private String createTargetScnRequestUrl() { // Adding checkpoint to the targetSCN request for supporting V3 bootstrap. It is unused in case of V2 bootstrap return String.format("/targetSCN?source=%s&checkPoint=%s", _checkpoint.getSnapshotSource(), _checkpoint.toString()); } @Override public void requestStartScn(Checkpoint checkpoint, DatabusBootstrapConnectionStateMessage stateReuse, String sourceNamesList) { _checkpoint = checkpoint; _callbackStateReuse = stateReuse; _sourcesNameList = sourceNamesList; _handler = null; if (!hasConnection()) { connect(State.START_SCN_REQUEST_CONNECT); } else { onStartScnConnectSuccess(); } } void onStartScnConnectSuccess() { _curState = State.START_SCN_REQUEST_WRITE; ChannelPipeline channelPipeline = _channel.getPipeline(); _readTimeOutHandler = (ExtendedReadTimeoutHandler)channelPipeline.get(GenericHttpClientPipelineFactory.READ_TIMEOUT_HANDLER_NAME); _readTimeOutHandler.start(channelPipeline.getContext(_readTimeOutHandler)); BootstrapStartScnHttpResponseProcessor sourcesResponseProcessor = new BootstrapStartScnHttpResponseProcessor(this, _callback, _callbackStateReuse, _checkpoint, _remoteExceptionHandler, _readTimeOutHandler); final String url = createStartScnRequestUrl(); LOG.info("Sending " + url); // Prepare the HTTP request. HttpRequest request = createEmptyRequest(url); sendRequest(request, new StartScnRequestResultListener(), sourcesResponseProcessor); } private String createStartScnRequestUrl() { return String.format("/startSCN?sources=%s&checkPoint=%s", _sourcesNameList, _checkpoint.toString()); } @Override public void requestStream(String sourcesIdList, DbusKeyFilter filter, int freeBufferSpace, Checkpoint cp, DatabusBootstrapConnectionStateMessage stateReuse) { _checkpoint = cp; _callbackStateReuse = stateReuse; _sourcesIdList = sourcesIdList; _freeBufferSpace = freeBufferSpace; _filter = filter; _handler = null; if (!hasConnection()) { connect(State.STREAM_REQUEST_CONNECT); } else { onStreamConnectSuccess(); } } void onStreamConnectSuccess() { _curState = State.STREAM_REQUEST_WRITE; ChannelPipeline channelPipeline = _channel.getPipeline(); _readTimeOutHandler = (ExtendedReadTimeoutHandler)channelPipeline.get(GenericHttpClientPipelineFactory.READ_TIMEOUT_HANDLER_NAME); _readTimeOutHandler.start(channelPipeline.getContext(_readTimeOutHandler)); StreamHttpResponseProcessor streamResponseProcessor = new StreamHttpResponseProcessor(this, _callback, _callbackStateReuse, _readTimeOutHandler); StringBuilder uriString = new StringBuilder(10240); boolean error = populateBootstrapRequestUrl(uriString); if (error) { return; } final String url = uriString.toString(); if (LOG.isDebugEnabled()) { LOG.debug("Sending " + url); } // Prepare the HTTP request. HttpRequest request = createEmptyRequest(url); sendRequest(request, new BootstrapRequestResultListener(), streamResponseProcessor); } private boolean populateBootstrapRequestUrl(StringBuilder uriString) { boolean error = false; ObjectMapper objMapper = new ObjectMapper(); String filterStr = null; if ( null != _filter) { try { filterStr = objMapper.writeValueAsString(_filter); } catch( Exception ex) { LOG.error("Got exception while serializing filter. Filter was : " + _filter, ex); error = true; onRequestFailure(uriString.toString(), ex); } } Formatter uriFmt = new Formatter(uriString); if ( null != filterStr) { uriFmt.format("/bootstrap?sources=%s&checkPoint=%s&output=binary&batchSize=%d&filter=%s", _sourcesIdList, _checkpoint.toString(), _freeBufferSpace, filterStr); } else { uriFmt.format("/bootstrap?sources=%s&checkPoint=%s&output=binary&batchSize=%d", _sourcesIdList, _checkpoint.toString(), _freeBufferSpace); } uriFmt.close(); //make the compiler shut up return error; } private void connect(State connectState) { _curState = connectState; connectWithListener(new MyConnectListener()); } private void onConnectSuccess(Channel channel) { switch (_curState) { case START_SCN_REQUEST_CONNECT: onStartScnConnectSuccess(); break; case TARGET_SCN_REQUEST_CONNECT: onTargetScnConnectSuccess(); break; case STREAM_REQUEST_CONNECT: onStreamConnectSuccess(); break; default: throw new RuntimeException("don't know what to do in state:" + _curState); } } /** * TODO : This method is overridden to get the _handler local to this class and not the * parent class. Once cleanup is performed to use _handler from parent class, this method * must be removed */ @Override protected GenericHttpResponseHandler getHandler() { return _handler; } private void onRequestFailure(HttpRequest req, Throwable cause) { onRequestFailure(null == req ? (String)null : req.getUri(), cause); } private void onRequestFailure(String req, Throwable cause) { LOG.info("request failure: req=" + req + " cause=" + cause); if(shouldIgnoreWriteTimeoutException(cause)) { LOG.error("got RequestFailure because of WriteTimeoutException"); return; } switch (_curState) { case START_SCN_REQUEST_CONNECT: case START_SCN_REQUEST_WRITE: _callbackStateReuse.switchToStartScnRequestError(); break; case TARGET_SCN_REQUEST_CONNECT: case TARGET_SCN_REQUEST_WRITE: _callbackStateReuse.switchToTargetScnRequestError(); break; case STREAM_REQUEST_CONNECT: case STREAM_REQUEST_WRITE: _callbackStateReuse.switchToStreamRequestError(); break; default: throw new RuntimeException("don't know what to do in state:" + _curState); } _callback.enqueueMessage(_callbackStateReuse); } private class MyConnectListener implements ConnectResultListener { /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.ConnectResultListener#onConnectSuccess(org.jboss.netty.channel.Channel) */ @Override public void onConnectSuccess(Channel channel) { NettyHttpDatabusBootstrapConnection.this.onConnectSuccess(channel); } /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.ConnectResultListener#onConnectFailure(java.lang.Throwable) */ @Override public void onConnectFailure(Throwable cause) { NettyHttpDatabusBootstrapConnection.this.onRequestFailure((String)null, cause); } } /** Callback for /startSCN request result */ private class StartScnRequestResultListener implements SendRequestResultListener { /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener#onSendRequestSuccess(org.jboss.netty.handler.codec.http.HttpRequest) */ @Override public void onSendRequestSuccess(HttpRequest req) { //Do nothing } /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener#onSendRequestFailure(org.jboss.netty.handler.codec.http.HttpRequest, java.lang.Throwable) */ @Override public void onSendRequestFailure(HttpRequest req, Throwable cause) { //TODO eventually the onRequestFailure can be expanded here onRequestFailure(req, cause); } } /** Callback for /targetSCN request result */ private class TargetScnRequestResultListener implements SendRequestResultListener { /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener#onSendRequestSuccess(org.jboss.netty.handler.codec.http.HttpRequest) */ @Override public void onSendRequestSuccess(HttpRequest req) { //Do nothing } /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener#onSendRequestFailure(org.jboss.netty.handler.codec.http.HttpRequest, java.lang.Throwable) */ @Override public void onSendRequestFailure(HttpRequest req, Throwable cause) { //TODO eventually the onRequestFailure can be expanded here onRequestFailure(req, cause); } } /** Callback for /bootstrap request result */ private class BootstrapRequestResultListener implements SendRequestResultListener { /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener#onSendRequestSuccess(org.jboss.netty.handler.codec.http.HttpRequest) */ @Override public void onSendRequestSuccess(HttpRequest req) { //Do nothing } /** * @see com.linkedin.databus.client.netty.AbstractNettyHttpConnection.SendRequestResultListener#onSendRequestFailure(org.jboss.netty.handler.codec.http.HttpRequest, java.lang.Throwable) */ @Override public void onSendRequestFailure(HttpRequest req, Throwable cause) { //TODO eventually the onRequestFailure can be expanded here onRequestFailure(req, cause); } } } class BootstrapTargetScnHttpResponseProcessor extends BaseHttpResponseProcessor { public static final String MODULE = BootstrapTargetScnHttpResponseProcessor.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); private final ActorMessageQueue _callback; private final DatabusBootstrapConnectionStateMessage _stateReuse; private final Checkpoint _checkpoint; private final RemoteExceptionHandler _remoteExceptionHandler; /** * Constructor * @param parent the AbstractNettyHttpConnection object that instantiated this * response processor * @param bootstrapPullThread callback to send the processed response or errors to * @param readStartScnState a message object to reuse for the callback * (TODO remove that: premature GC optimization) * @param readTimeOutHandler the ReadTimeoutHandler for the connection handled by this * response handler. */ public BootstrapTargetScnHttpResponseProcessor(AbstractNettyHttpConnection parent, ActorMessageQueue bootstrapPullThread, DatabusBootstrapConnectionStateMessage readStartScnState, Checkpoint checkpoint, RemoteExceptionHandler remoteExceptionHandler, ExtendedReadTimeoutHandler readTimeoutHandler) { super(parent, readTimeoutHandler); _callback = bootstrapPullThread; _stateReuse = readStartScnState; _checkpoint = checkpoint; _remoteExceptionHandler = remoteExceptionHandler; } @Override public void finishResponse() throws Exception { super.finishResponse(); if (_errorHandled) { return; } try { String exceptionName = RemoteExceptionHandler.getExceptionName(_decorated); Throwable remoteException = _remoteExceptionHandler.getException(_decorated); if (null != remoteException && remoteException instanceof BootstrapDatabaseTooOldException) { _remoteExceptionHandler.handleException(remoteException); } else if (null != exceptionName) { LOG.error("/targetScn response error: " + RemoteExceptionHandler.getExceptionMessage(_decorated)); _stateReuse.switchToTargetScnResponseError(); } else { InputStream bodyStream = Channels.newInputStream(_decorated); ObjectMapper mapper = new ObjectMapper(); String scnString = mapper.readValue(bodyStream, String.class); LOG.info("targetScn:" + scnString); long targetScn = Long.parseLong(scnString); _stateReuse.switchToTargetScnSuccess(); // make sure we are in the expected mode -- sanity checks Checkpoint ckpt = _checkpoint; if (ckpt.getConsumptionMode() != DbusClientMode.BOOTSTRAP_SNAPSHOT) { throw new InvalidCheckpointException("TargetScnResponseProcessor:" + " expecting in client mode: " + DbusClientMode.BOOTSTRAP_SNAPSHOT, ckpt); } else if (! ckpt.isSnapShotSourceCompleted()) { throw new InvalidCheckpointException("TargetScnResponseProcessor: current snapshot source not completed", ckpt); } LOG.info("Target SCN " + targetScn + " received for bootstrap catchup source " + ckpt.getCatchupSource() + " after completion of snapshot source " + ckpt.getSnapshotSource()); ckpt.setBootstrapTargetScn(targetScn); } } catch (Exception ex) { LOG.error("/targetScn response error:" + ex.getMessage(), ex); _stateReuse.switchToTargetScnResponseError(); } _callback.enqueueMessage(_stateReuse); } @Override public void startResponse(HttpResponse response) throws Exception { _decorated = new ChunkedBodyReadableByteChannel(); super.startResponse(response); } @Override public void handleChannelException(Throwable cause) { DbusPrettyLogUtils.logExceptionAtError("Exception during /targetSCN response: ", cause, LOG); if (_responseStatus != ResponseStatus.CHUNKS_FINISHED) { LOG.info("Enqueueing TargetSCN Response Error State to Puller Queue"); _stateReuse.switchToTargetScnResponseError(); _callback.enqueueMessage(_stateReuse); } else { LOG.info("Skipping Enqueueing TargetSCN Response Error State to Puller Queue"); } super.handleChannelException(cause); } } class BootstrapStartScnHttpResponseProcessor extends BaseHttpResponseProcessor { public static final String MODULE = BootstrapStartScnHttpResponseProcessor.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); private final ActorMessageQueue _callback; private final DatabusBootstrapConnectionStateMessage _stateReuse; private final Checkpoint _checkpoint; private final RemoteExceptionHandler _remoteExceptionHandler; /** * Constructor * @param parent the AbstractNettyHttpConnection object that instantiated this * response processor * @param bootstrapPullThread callback to send the processed response or errors to * @param readStartScnState a message object to reuse for the callback * (TODO remove that: premature GC optimization) * @param readTimeOutHandler the ReadTimeoutHandler for the connection handled by this * response handler. */ public BootstrapStartScnHttpResponseProcessor(AbstractNettyHttpConnection parent, ActorMessageQueue bootstrapPullThread, DatabusBootstrapConnectionStateMessage readStartScnState, Checkpoint checkpoint, RemoteExceptionHandler remoteExceptionHandler, ExtendedReadTimeoutHandler readTimeOutHandler) { super(parent, readTimeOutHandler); _callback = bootstrapPullThread; _stateReuse = readStartScnState; _checkpoint = checkpoint; _remoteExceptionHandler = remoteExceptionHandler; } @Override public void finishResponse() throws Exception { super.finishResponse(); if (_errorHandled) { return; } try { String exceptionName = RemoteExceptionHandler.getExceptionName(_decorated); Throwable remoteException = _remoteExceptionHandler.getException(_decorated); if (null != remoteException && remoteException instanceof BootstrapDatabaseTooOldException) { _remoteExceptionHandler.handleException(remoteException); } else if (null != exceptionName) { LOG.error("/startScn response error: " + RemoteExceptionHandler.getExceptionMessage(_decorated)); _stateReuse.switchToStartScnResponseError(); LOG.error("Failed to process /startscn response"); } else { String hostHdr = DbusConstants.UNKNOWN_HOST; String svcHdr = DbusConstants.UNKNOWN_SERVICE_ID; if (null != getParent()) { hostHdr = getParent().getRemoteHost(); svcHdr = getParent().getRemoteService(); LOG.info("initiated bootstrap sesssion to host " + hostHdr + " service " + svcHdr); } InputStream bodyStream = Channels.newInputStream(_decorated); ObjectMapper mapper = new ObjectMapper(); String scnString = mapper.readValue(bodyStream, String.class); ServerInfo serverInfo = null; String serverHostPort = _decorated.getMetadata(DbusConstants.SERVER_INFO_HOSTPORT_HEADER_PARAM); try { serverInfo = ServerInfo.buildServerInfoFromHostPort(serverHostPort, DbusConstants.HOSTPORT_DELIMITER); } catch(Exception ex) { LOG.error("Unable to extract Boostrap Server info from StartSCN response. ServerInfo was :" + serverHostPort, ex); } LOG.info("Response startScn:" + scnString + ", from bootstrap Server :" + serverHostPort); long startScn = Long.parseLong(scnString); Checkpoint ckpt = _checkpoint; if (startScn < 0) { LOG.error("unexpected value for startSCN: " + startScn); _stateReuse.switchToStartScnResponseError(); } else if (ckpt.getConsumptionMode() != DbusClientMode.BOOTSTRAP_SNAPSHOT) { LOG.error("StartScnResponseProcessor:" + " expecting in client mode: " + DbusClientMode.BOOTSTRAP_SNAPSHOT + " while in the incorrect mode: " + ckpt.getConsumptionMode()); } else { LOG.info("Start SCN " + startScn + " received for bootstrap snapshot source " + ckpt.getSnapshotSource()); ckpt.setBootstrapStartScn(startScn); ckpt.setBootstrapServerInfo(serverHostPort); /* * No need to create a seperate BootstrapConnection as we are guaranteed to have a bootstrap Connection * at this point. */ _stateReuse.switchToStartScnSuccess(_checkpoint, null, serverInfo); } } } catch (Exception ex) { LOG.error("Failed to process /startscn response", ex); _stateReuse.switchToStartScnResponseError(); } _callback.enqueueMessage(_stateReuse); } @Override public void startResponse(HttpResponse response) throws Exception { _decorated = new ChunkedBodyReadableByteChannel(); super.startResponse(response); } @Override public void handleChannelException(Throwable cause) { DbusPrettyLogUtils.logExceptionAtError("Exception during /startSCN response: ", cause, LOG); if (_responseStatus != ResponseStatus.CHUNKS_FINISHED) { LOG.info("Enqueueing StartSCN Response Error State to Puller Queue"); _stateReuse.switchToStartScnResponseError(); _callback.enqueueMessage(_stateReuse); } else { LOG.info("Skipping Enqueueing StartSCN Response Error State to Puller Queue"); } super.handleChannelException(cause); } }