/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * 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. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.server.distributed; import com.orientechnologies.common.exception.OException; import com.orientechnologies.common.io.OIOException; import com.orientechnologies.orient.client.binary.OChannelBinarySynchClient; import com.orientechnologies.orient.core.OConstants; import com.orientechnologies.orient.core.config.OContextConfiguration; import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol; import java.io.IOException; import java.util.Date; import java.util.concurrent.Executors; /** * Remote server channel. * * @author Luca Garulli */ public class ORemoteServerChannel { private final ODistributedServerManager manager; private final String url; private final String remoteHost; private final int remotePort; private final String userName; private final String userPassword; private final String server; private OChannelBinarySynchClient channel; private static final int MAX_RETRY = 3; private static final String CLIENT_TYPE = "OrientDB Server"; private static final boolean COLLECT_STATS = false; private int sessionId = -1; private byte[] sessionToken; private OContextConfiguration contextConfig = new OContextConfiguration(); private Date createdOn = new Date(); private volatile int totalConsecutiveErrors = 0; private final static int MAX_CONSECUTIVE_ERRORS = 10; public ORemoteServerChannel(final ODistributedServerManager manager, final String iServer, final String iURL, final String user, final String passwd) throws IOException { this.manager = manager; this.server = iServer; this.url = iURL; this.userName = user; this.userPassword = passwd; final int sepPos = iURL.lastIndexOf(":"); remoteHost = iURL.substring(0, sepPos); remotePort = Integer.parseInt(iURL.substring(sepPos + 1)); connect(); } public interface OStorageRemoteOperation<T> { T execute() throws IOException; } public void sendRequest(final ODistributedRequest request) { networkOperation(OChannelBinaryProtocol.DISTRIBUTED_REQUEST, new OStorageRemoteOperation<Object>() { @Override public Object execute() throws IOException { request.toStream(channel.getDataOutput()); channel.flush(); return null; } }, "Cannot send distributed request", MAX_RETRY, true); } public void sendResponse(final ODistributedResponse response) { networkOperation(OChannelBinaryProtocol.DISTRIBUTED_RESPONSE, new OStorageRemoteOperation<Object>() { @Override public Object execute() throws IOException { response.toStream(channel.getDataOutput()); channel.flush(); return null; } }, "Cannot send response back to the sender node '" + response.getSenderNodeName() + "'", MAX_RETRY, true); } public void connect() throws IOException { channel = new OChannelBinarySynchClient(remoteHost, remotePort, null, contextConfig, OChannelBinaryProtocol.CURRENT_PROTOCOL_VERSION); networkOperation(OChannelBinaryProtocol.REQUEST_CONNECT, new OStorageRemoteOperation<Void>() { @Override public Void execute() throws IOException { channel.writeString(CLIENT_TYPE).writeString(OConstants.ORIENT_VERSION) .writeShort((short) OChannelBinaryProtocol.CURRENT_PROTOCOL_VERSION).writeString("0"); channel.writeString(ODatabaseDocumentTx.getDefaultSerializer().toString()); channel.writeBoolean(false); channel.writeBoolean(false); // SUPPORT PUSH channel.writeBoolean(COLLECT_STATS); // COLLECT STATS channel.writeString(userName); channel.writeString(userPassword); channel.flush(); channel.beginResponse(false); sessionId = channel.readInt(); sessionToken = channel.readBytes(); if (sessionToken.length == 0) { sessionToken = null; } return null; } }, "Cannot connect to the remote server '" + url + "'", MAX_RETRY, false); } public void close() { if (channel != null) channel.close(); sessionId = -1; sessionToken = null; } protected synchronized <T> T networkOperation(final byte operationId, final OStorageRemoteOperation<T> operation, final String errorMessage, final int maxRetry, final boolean autoReconnect) { Exception lastException = null; for (int retry = 1; retry <= maxRetry && totalConsecutiveErrors < MAX_CONSECUTIVE_ERRORS; ++retry) { try { channel.setWaitResponseTimeout(); channel.beginRequest(operationId, sessionId, sessionToken); T result = operation.execute(); // RESET ERRORS totalConsecutiveErrors = 0; return result; } catch (Exception e) { // DIRTY CONNECTION, CLOSE IT AND RE-ACQUIRE A NEW ONE lastException = e; handleNewError(); close(); if (!autoReconnect) break; if (!manager.isNodeAvailable(server)) break; ODistributedServerLog.warn(this, manager.getLocalNodeName(), server, ODistributedServerLog.DIRECTION.OUT, "Error on sending message to distributed node (%s) retrying (%d/%d)", lastException.toString(), retry, maxRetry); if (retry > 1) try { Thread.sleep(100 * (retry * 2)); } catch (InterruptedException e1) { break; } try { connect(); // RESET ERRORS totalConsecutiveErrors = 0; } catch (IOException e1) { lastException = e1; handleNewError(); ODistributedServerLog.warn(this, manager.getLocalNodeName(), server, ODistributedServerLog.DIRECTION.OUT, "Error on reconnecting to distributed node (%s)", lastException.toString()); } } } if (lastException == null) handleNewError(); throw OException.wrapException(new ODistributedException(errorMessage), lastException); } public ODistributedServerManager getManager() { return manager; } public String getServer() { return server; } public Date getCreatedOn() { return createdOn; } private void handleNewError() { totalConsecutiveErrors++; if (totalConsecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { ODistributedServerLog.warn(this, manager.getLocalNodeName(), server, ODistributedServerLog.DIRECTION.OUT, "Reached %d consecutive errors on connection, remove the server '%s' from the cluster", totalConsecutiveErrors, server); // REMOVE THE SERVER ASYNCHRONOUSLY Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { try { manager.removeServer(server, true); } catch (Throwable e) { ODistributedServerLog.warn(this, manager.getLocalNodeName(), server, ODistributedServerLog.DIRECTION.OUT, "Error on removing server '%s' from the cluster", server); } } }); throw new OIOException("Reached " + totalConsecutiveErrors + " consecutive errors on connection, remove the server '" + server + "' from the cluster"); } } }