From ccc4127f23f63214f4dc2f94d26a021a3ec2eec6 Mon Sep 17 00:00:00 2001
From: pgamba <pgamba@localhost>
Date: Wed, 10 Jun 2009 08:43:50 +0000
Subject: [PATCH] External Changelog - first step - related issues 495, 519
---
opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java | 215
opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java | 11
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java | 60
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java | 160
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java | 12
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java | 377 +
opendj-sdk/opends/src/messages/messages/protocol.properties | 3
opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java | 3432 ++++----------
opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java | 19
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java | 12
opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java | 542 +
opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java | 2
opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java | 109
opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java | 52
opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java | 9
opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java | 791 +++
opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java | 4
opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java | 173
opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java | 200
opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java | 48
opendj-sdk/opends/src/messages/messages/replication.properties | 9
opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java | 38
opendj-sdk/opends/resource/config/config.ldif | 1
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java | 189
opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java | 20
opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java | 5
opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java | 309 +
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java | 1569 ++++++
opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java | 1300 +++++
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java | 12
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java | 100
opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java | 724 +++
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java | 432 +
opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java | 912 +++
opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java | 220
opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java | 85
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java | 27
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java | 6
opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java | 7
opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java | 2
opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java | 1551 ++++++
opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java | 170
opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java | 306 +
43 files changed, 11,479 insertions(+), 2,746 deletions(-)
diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index a6d685c..05f9afe 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -78,6 +78,7 @@
ds-cfg-global-aci: (target="ldap:///")(targetscope="base")(targetattr="objectClass||namingContexts||supportedAuthPasswordSchemes||supportedControl||supportedExtension||supportedFeatures||supportedLDAPVersion||supportedSASLMechanisms||vendorName||vendorVersion")(version 3.0; acl "User-Visible Root DSE Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetattr="createTimestamp||creatorsName||modifiersName||modifyTimestamp||entryDN||entryUUID||subschemaSubentry")(version 3.0; acl "User-Visible Operational Attributes"; allow (read,search,compare) userdn="ldap:///anyone";)
ds-cfg-global-aci: (target="ldap:///dc=replicationchanges")(targetattr="*")(version 3.0; acl "Replication backend access"; deny (all) userdn="ldap:///anyone";)
+ds-cfg-global-aci: (target="ldap:///cn=changelog")(targetattr="*")(version 3.0; acl "External changelog access"; deny (all) userdn="ldap:///anyone";)
cn: Access Control Handler
ds-cfg-java-class: org.opends.server.authorization.dseecompat.AciHandler
ds-cfg-enabled: true
diff --git a/opendj-sdk/opends/src/messages/messages/protocol.properties b/opendj-sdk/opends/src/messages/messages/protocol.properties
index ed569e5..337198c 100644
--- a/opendj-sdk/opends/src/messages/messages/protocol.properties
+++ b/opendj-sdk/opends/src/messages/messages/protocol.properties
@@ -1411,4 +1411,7 @@
SEVERE_ERR_SNMP_CONNHANDLER_OPENDMK_JARFILES_NOT_OPERATIONAL_1507=The required \
classes could not be loaded using jar file '%s'. Verify that the jar file \
is not corrupted
+MILD_ERR_CANNOT_DECODE_CONTROL_VALUE_1508=Cannot decode the provided \
+ control %s because an error occurred while attempting to \
+ decode the control value: %s
diff --git a/opendj-sdk/opends/src/messages/messages/replication.properties b/opendj-sdk/opends/src/messages/messages/replication.properties
index 4d27c47..b77bb30 100644
--- a/opendj-sdk/opends/src/messages/messages/replication.properties
+++ b/opendj-sdk/opends/src/messages/messages/replication.properties
@@ -365,4 +365,11 @@
SEVERE_ERR_COULD_NOT_START_REPLICATION_154=The Replication was not started \
on base-dn %s : %s
MILD_ERR_ERROR_RETRIEVING_MONITOR_DATA_155=Error retrieving monitoring data: %s
-SEVERE_ERR_BYTE_COUNT_156=The Server Handler byte count is not correct (Fixed)
\ No newline at end of file
+SEVERE_ERR_EXCEPTION_LOCKING_RS_DOMAIN_156=Caught exception when locking \
+ the replication server domain: %s
+SEVERE_ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE_157=Replication \
+ protocol error. Bad message type. %s received, %s required
+SEVERE_ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED_158=The provided cookie is \
+ valid in the current context due to %s. Full resync is required
+SEVERE_ERR_BYTE_COUNT_159=The Server Handler byte count is not correct (Fixed)
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java b/opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java
new file mode 100644
index 0000000..402e4d1
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/controls/EntryChangelogNotificationControl.java
@@ -0,0 +1,173 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.controls;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_NO_CONTROL_VALUE;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.protocols.asn1.ASN1Constants.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Reader;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+
+
+/**
+ * This class implements the ECL cookie control.
+ * It may be included in entries returned in response to a search or
+ * persistent search operation.
+ */
+public class EntryChangelogNotificationControl
+ extends Control
+ {
+ // The tracer object for the debug logger.
+ private static final DebugTracer TRACER = getTracer();
+
+ // The cookie value - payload of this control.
+ private String cookie;
+
+ /**
+ * ControlDecoder implentation to decode this control from a ByteString.
+ */
+ private final static class Decoder
+ implements ControlDecoder<EntryChangelogNotificationControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public EntryChangelogNotificationControl decode(
+ boolean isCritical, ByteString value) throws DirectoryException
+ {
+ if (value == null)
+ {
+ // FIXME:ECL Create error messages dedicated to this control
+ Message message = ERR_ECN_NO_CONTROL_VALUE.get();
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
+ }
+
+
+ String cookie = null;
+ ASN1Reader reader = ASN1.getReader(value);
+ try
+ {
+ reader.readStartSequence();
+ cookie = reader.readOctetStringAsString();
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ Message message =
+ ERR_ECN_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e);
+ }
+ return new EntryChangelogNotificationControl(isCritical, cookie);
+ }
+
+ public String getOID()
+ {
+ return OID_ECL_COOKIE_EXCHANGE_CONTROL;
+ }
+
+ }
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<EntryChangelogNotificationControl>
+ DECODER = new Decoder();
+
+ /**
+ * Creates a new entry change notification control with the provided
+ * information.
+ *
+ * @param isCritical Indicates whether this control should be
+ * considered critical in processing the
+ * request.
+ * @param cookie The provided cookie value.
+ */
+ public EntryChangelogNotificationControl(boolean isCritical,
+ String cookie)
+ {
+ super(OID_ECL_COOKIE_EXCHANGE_CONTROL, isCritical);
+ this.cookie = cookie;
+ }
+
+ /**
+ * Writes this control's value to an ASN.1 writer. The value (if any)
+ * must be written as an ASN1OctetString.
+ *
+ * @param writer The ASN.1 output stream to write to.
+ * @throws IOException If a problem occurs while writing to the stream.
+ */
+ public void writeValue(ASN1Writer writer) throws IOException {
+ writer.writeStartSequence(UNIVERSAL_OCTET_STRING_TYPE);
+ writer.writeOctetString(cookie.toString());
+ writer.writeEndSequence();
+ }
+
+
+
+ /**
+ * Retrieves the change type for this entry change notification control.
+ *
+ * @return The change type for this entry change notification control.
+ */
+ public String getCookie()
+ {
+ return cookie;
+ }
+
+ /**
+ * Appends a string representation of this entry change notification control
+ * to the provided buffer.
+ *
+ * @param buffer The buffer to which the information should be appended.
+ */
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("EntryChangelogNotificationControl(cookie=");
+ buffer.append(cookie.toString());
+ buffer.append(")");
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java b/opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java
new file mode 100644
index 0000000..8747c4f
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/controls/ExternalChangelogRequestControl.java
@@ -0,0 +1,170 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.controls;
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.server.protocols.asn1.ASN1Constants.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Reader;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+
+
+
+/**
+ * This class implements the persistent search control defined in
+ * draft-ietf-ldapext-psearch. It makes it possible for clients to be notified
+ * of changes to information in the Directory Server as they occur.
+ */
+public class ExternalChangelogRequestControl
+ extends Control
+{
+ private static final DebugTracer TRACER = getTracer();
+
+ private MultiDomainServerState cookie;
+
+ /**
+ * ControlDecoder implentation to decode this control from a ByteString.
+ */
+ private final static class Decoder
+ implements ControlDecoder<ExternalChangelogRequestControl>
+ {
+ /**
+ * {@inheritDoc}
+ */
+ public ExternalChangelogRequestControl decode(boolean isCritical,
+ ByteString value)
+ throws DirectoryException
+ {
+ if (value == null)
+ {
+ // FIXME:ECL In the request cookie, empty value is currently rejected.
+ Message message = ERR_PSEARCH_NO_CONTROL_VALUE.get();
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
+ }
+
+ ASN1Reader reader = ASN1.getReader(value);
+ MultiDomainServerState mdss;
+ String mdssValue = null;
+ try
+ {
+ mdssValue = reader.readOctetStringAsString();
+ mdss = new MultiDomainServerState(mdssValue);
+ }
+ catch (Exception e)
+ {
+ try
+ {
+ mdssValue = value.toString();
+ mdss = new MultiDomainServerState(mdssValue);
+ }
+ catch (Exception e2)
+ {
+ Message message =
+ ERR_CANNOT_DECODE_CONTROL_VALUE.get(
+ getOID() + " x=" + value.toHex() + " v="
+ + mdssValue , getExceptionMessage(e).toString());
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e);
+ }
+ }
+ return new ExternalChangelogRequestControl(isCritical, mdss);
+ }
+
+ public String getOID()
+ {
+ return OID_ECL_COOKIE_EXCHANGE_CONTROL;
+ }
+
+ }
+
+ /**
+ * The Control Decoder that can be used to decode this control.
+ */
+ public static final ControlDecoder<ExternalChangelogRequestControl> DECODER =
+ new Decoder();
+
+ /**
+ * Create a new external change log request control to contain the cookie.
+ * @param isCritical Specifies whether the control is critical.
+ * @param cookie Specifies the cookie value.
+ */
+ public ExternalChangelogRequestControl(boolean isCritical,
+ MultiDomainServerState cookie)
+ {
+ super(OID_ECL_COOKIE_EXCHANGE_CONTROL, isCritical);
+ this.cookie = cookie;
+ }
+
+ /**
+ * Returns the cookie value.
+ * @return The cookie value.
+ */
+ public MultiDomainServerState getCookie()
+ {
+ return this.cookie;
+ }
+
+ /**
+ * Dump a string representation of this object to the provided bufer.
+ * @param buffer The provided buffer.
+ */
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append("ExternalChangelogRequestControl(cookie=");
+ this.cookie.toString(buffer);
+ buffer.append(")");
+ }
+
+ /**
+ * Writes this control's value to an ASN.1 writer. The value
+ * (if any) must be written as an ASN1OctetString.
+ *
+ * @param writer The ASN.1 writer to use.
+ * @throws IOException If a problem occurs while writing to the
+ * stream.
+ */
+ protected void writeValue(ASN1Writer writer)
+ throws IOException
+ {
+ writer.writeStartSequence(UNIVERSAL_OCTET_STRING_TYPE);
+ writer.writeOctetString(this.cookie.toString());
+ writer.writeEndSequence();
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java b/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
index 5453b3c..26feafc 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PersistentSearch.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
+ * Copyright 2006-2009 Sun Microsystems, Inc.
*/
package org.opends.server.core;
@@ -240,6 +240,15 @@
}
+ /**
+ * Get the search operation associated with this persistent search.
+ *
+ * @return The search operation associated with this persistent search.
+ */
+ public SearchOperation getSearchOperation()
+ {
+ return searchOperation;
+ }
/**
* Notifies the persistent searches that an entry has been added.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java b/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java
index 37455d6..ecc472a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/loggers/debug/DebugLogger.java
@@ -453,7 +453,7 @@
*/
public static boolean debugEnabled()
{
- return enabled;
+ return true;
}
/**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java
new file mode 100644
index 0000000..927537b
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ExternalChangeLogSession.java
@@ -0,0 +1,52 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.common;
+
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.types.DirectoryException;
+
+/**
+ * This interface defines a session used to search the external changelog
+ * in the Directory Server.
+ */
+public interface ExternalChangeLogSession
+{
+ /**
+ * Returns the next message available for the ECL (blocking).
+ * @return the next available message from the ECL.
+ * @throws DirectoryException When an error occurs.
+ */
+ public abstract ECLUpdateMsg getNextUpdate()
+ throws DirectoryException;
+
+ /**
+ * Closes the session.
+ * @throws DirectoryException when needed.
+ */
+ public abstract void close()
+ throws DirectoryException;
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java
new file mode 100644
index 0000000..7a63049
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/MultiDomainServerState.java
@@ -0,0 +1,215 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.common;
+
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.util.Iterator;
+import java.util.TreeMap;
+
+import org.opends.server.loggers.debug.DebugTracer;
+
+
+/**
+ * This object is used to store a list of ServerState object, one by
+ * replication domain. Globally, it is the generalization of ServerState
+ * (that applies to one domain) to a list of domains.
+ */
+public class MultiDomainServerState implements Iterable<String>
+{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = getTracer();
+
+ /**
+ * The list of (domain service id, ServerState).
+ */
+ private TreeMap<String, ServerState> list;
+
+ /**
+ * Creates a new empty object.
+ */
+ public MultiDomainServerState()
+ {
+ list = new TreeMap<String, ServerState>();
+ }
+
+ /**
+ * Empty the object..
+ * After this call the object will be in the same state as if it
+ * was just created.
+ */
+ public void clear()
+ {
+ synchronized (this)
+ {
+ list.clear();
+ }
+ }
+
+ /**
+ * Update the ServerState of the provided serviceId with the
+ * replication change number provided.
+ *
+ * @param serviceId The provided serviceId.
+ * @param changeNumber The provided ChangeNumber.
+ *
+ * @return a boolean indicating if the update was meaningful.
+ */
+ public boolean update(String serviceId, ChangeNumber changeNumber)
+ {
+ if (changeNumber == null)
+ return false;
+
+ synchronized(this)
+ {
+ Short serverId = changeNumber.getServerId();
+ ServerState oldServerState = list.get(serviceId);
+ if (oldServerState == null)
+ oldServerState = new ServerState();
+
+ if (changeNumber.newer(oldServerState.getMaxChangeNumber(serverId)))
+ {
+ oldServerState.update(changeNumber);
+ list.put(serviceId,oldServerState);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Create an object from a string representation.
+ * @param mdss The provided string representation of the state.
+ */
+ public MultiDomainServerState(String mdss)
+ {
+ list = new TreeMap<String, ServerState>();
+ if ((mdss != null) &&
+ (mdss.length()>0))
+ {
+ String[] domains = mdss.split(";");
+ for (String domain : domains)
+ {
+ String[] fields = domain.split(":");
+ String name = fields[0];
+ ServerState ss = new ServerState();
+ if (fields.length>1)
+ {
+ String strState = fields[1].trim();
+ String[] strCN = strState.split(" ");
+ for (String sr : strCN)
+ {
+ ChangeNumber fromChangeNumber = new ChangeNumber(sr);
+ ss.update(fromChangeNumber);
+ }
+ }
+ this.list.put(name, ss);
+ }
+ }
+ }
+
+ /**
+ * Returns a string representation of this object.
+ * @return The string representation.
+ */
+ public String toString()
+ {
+ String res = "";
+ if ((list != null) && (!list.isEmpty()))
+ {
+ for (String serviceId : list.keySet())
+ {
+ ServerState ss = list.get(serviceId);
+ res += serviceId + ":" + ss.toString();
+ res += ";";
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Dump a string representation in the provided buffer.
+ * @param buffer The provided buffer.
+ */
+ public void toString(StringBuilder buffer)
+ {
+ buffer.append(this.toString());
+ }
+
+
+ /**
+ * Tests if the state is empty.
+ *
+ * @return True if the state is empty.
+ */
+ public boolean isEmpty()
+ {
+ return list.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<String> iterator()
+ {
+ return list.keySet().iterator();
+ }
+
+ /**
+ * Test if this object equals the provided other object.
+ * @param other The other object with which we want to test equality.
+ * @return Returns True if this equals other, else return false.
+ */
+ public boolean equalsTo(MultiDomainServerState other)
+ {
+ return ((this.cover(other)) && (other.cover(this)));
+ }
+
+ /**
+ * Test if this object covers the provided covered object.
+ * @param covered The provided object.
+ * @return true when this covers the provided object.
+ */
+ public boolean cover(MultiDomainServerState covered)
+ {
+ for (String serviceId : covered.list.keySet())
+ {
+ ServerState state = list.get(serviceId);
+ ServerState coveredState = covered.list.get(serviceId);
+ if ((coveredState == null) || (!state.cover(coveredState)))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java
index 0eac1cf..bf7565b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/common/ServerState.java
@@ -122,6 +122,8 @@
/**
* Get the length of the next String encoded in the in byte array.
+ * This method is used to cut the different parts (server ids, change number)
+ * of a server state.
*
* @param in the byte array where to calculate the string.
* @param pos the position whre to start from in the byte array.
@@ -135,7 +137,7 @@
while (in[offset++] != 0)
{
if (offset >= in.length)
- throw new DataFormatException("byte[] is not a valid modify msg");
+ throw new DataFormatException("byte[] is not a valid server state");
length++;
}
return length;
@@ -251,7 +253,6 @@
* Return the text representation of ServerState.
* @return the text representation of ServerState
*/
- @Override
public String toString()
{
StringBuilder buffer = new StringBuilder();
@@ -261,9 +262,11 @@
for (Short key : list.keySet())
{
ChangeNumber change = list.get(key);
+ buffer.append(change.toString());
buffer.append(" ");
- buffer.append(change.toStringUI());
}
+ if (!list.isEmpty())
+ buffer.deleteCharAt(buffer.length()-1);
}
return buffer.toString();
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
index 8110a81..68031e7 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/plugin/MultimasterReplication.java
@@ -817,4 +817,23 @@
{
return domains.size();
}
+
+ /**
+ * Gets the baseDn of the domains that have a private backend.
+ * @return The private baseDN.
+ */
+ public static ArrayList<String> getPrivateDomains()
+ {
+ ArrayList<String> privateDNs = new ArrayList<String>();
+
+ for (LDAPReplicationDomain domain : domains.values())
+ {
+ Backend b = domain.getBackend();
+ if (b != null)
+ if (b.isPrivateBackend())
+ privateDNs.add(domain.getBaseDN().toNormalizedString());
+ }
+ return privateDNs;
+ }
+
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java
new file mode 100644
index 0000000..0e8e8b5
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ECLUpdateMsg.java
@@ -0,0 +1,189 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.protocol;
+
+import java.io.UnsupportedEncodingException;
+import java.util.zip.DataFormatException;
+import org.opends.server.replication.common.MultiDomainServerState;
+
+/**
+ * Container for the ECL information sent from the ReplicationServer
+ * to the client part (either broker over the protocol, or ECLSession).
+ */
+public class ECLUpdateMsg extends ReplicationMsg
+{
+ // The replication change returned.
+ private final LDAPUpdateMsg updateMsg;
+
+ // The serviceId (baseDN) of the domain to which applies the change.
+ private final String serviceId;
+
+ // The value of the cookie updated with the current change
+ private MultiDomainServerState cookie;
+
+ /**
+ * Creates a new message.
+ * @param update The provided update.
+ * @param cookie The provided cookie value
+ * @param serviceId The provided serviceId.
+ */
+ public ECLUpdateMsg(LDAPUpdateMsg update, MultiDomainServerState cookie,
+ String serviceId)
+ {
+ this.cookie = cookie;
+ this.serviceId = serviceId;
+ this.updateMsg = update;
+ }
+
+ /**
+ * Creates a new message from its encoded form.
+ *
+ * @param in The byte array containing the encoded form of the message.
+ * @throws DataFormatException If the byte array does not contain
+ * a valid encoded form of the message.
+ * @throws UnsupportedEncodingException when it occurs.
+ * @throws NotSupportedOldVersionPDUException when it occurs.
+ */
+ public ECLUpdateMsg(byte[] in)
+ throws DataFormatException,
+ UnsupportedEncodingException,
+ NotSupportedOldVersionPDUException
+ {
+ try
+ {
+ if (in[0] != MSG_TYPE_ECL_UPDATE)
+ {
+ throw new DataFormatException("byte[] is not a valid " +
+ this.getClass().getCanonicalName());
+ }
+ int pos = 1;
+
+ // Decode the cookie
+ int length = getNextLength(in, pos);
+ String cookieStr = new String(in, pos, length, "UTF-8");
+ this.cookie = new MultiDomainServerState(cookieStr);
+ pos += length + 1;
+
+ // Decode the serviceId
+ length = getNextLength(in, pos);
+ this.serviceId = new String(in, pos, length, "UTF-8");
+ pos += length + 1;
+
+ // Decode the msg
+ /* Read the mods : all the remaining bytes but the terminating 0 */
+ length = in.length - pos - 1;
+ byte[] encodedMsg = new byte[length];
+ System.arraycopy(in, pos, encodedMsg, 0, length);
+ ReplicationMsg rmsg = ReplicationMsg.generateMsg(encodedMsg);
+ this.updateMsg = (LDAPUpdateMsg)rmsg;
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new DataFormatException("UTF-8 is not supported by this jvm.");
+ }
+ }
+
+ /**
+ * Getter for the cookie value.
+ * @return The cookie value.
+ */
+ public MultiDomainServerState getCookie()
+ {
+ return cookie;
+ }
+
+ /**
+ * Setter for the cookie value.
+ * @param cookie The provided cookie value.
+ */
+ public void setCookie(MultiDomainServerState cookie)
+ {
+ this.cookie = cookie;
+ }
+
+ /**
+ * Getter for the serviceId.
+ * @return The serviceId.
+ */
+ public String getServiceId()
+ {
+ return serviceId;
+ }
+
+ /**
+ * Getter for the message.
+ * @return The included replication message.
+ */
+ public UpdateMsg getUpdateMsg()
+ {
+ return updateMsg;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "ECLUpdateMsg:[" +
+ "updateMsg: " + updateMsg +
+ "cookie: " + cookie +
+ "serviceId: " + serviceId + "]";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getBytes() throws UnsupportedEncodingException
+ {
+ byte[] byteCookie = String.valueOf(cookie).getBytes("UTF-8");
+ byte[] byteServiceId = String.valueOf(serviceId).getBytes("UTF-8");
+ byte[] byteUpdateMsg = updateMsg.getBytes();
+
+ int length = 1 + byteCookie.length +
+ 1 + byteServiceId.length +
+ 1 + byteUpdateMsg.length + 1;
+
+ byte[] resultByteArray = new byte[length];
+
+ /* Encode type */
+ resultByteArray[0] = MSG_TYPE_ECL_UPDATE;
+ int pos = 1;
+
+ // Encode cookie
+ pos = addByteArray(byteCookie, resultByteArray, pos);
+
+ // Encode serviceid
+ pos = addByteArray(byteServiceId, resultByteArray, pos);
+
+ // Encode msg
+ pos = addByteArray(byteUpdateMsg, resultByteArray, pos);
+
+ return resultByteArray;
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
index 865d97c..96b0f6a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/HeartbeatMonitor.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2007-2008 Sun Microsystems, Inc.
+ * Copyright 2007-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.protocol;
@@ -103,9 +103,8 @@
boolean gotOneFailure = false;
if (debugEnabled())
{
- TRACER.debugInfo("Heartbeat monitor is starting, expected interval is " +
- heartbeatInterval +
- stackTraceToSingleLineString(new Exception()));
+ TRACER.debugInfo(this + " is starting, expected interval is " +
+ heartbeatInterval);
}
try
{
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
index f1bdb60..0201146 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
+ * Copyright 2006-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.protocol;
@@ -167,9 +167,13 @@
pos += length + 1;
}
- /*
- * read the ServerState
- */
+ // Read the ServerState
+ // Caution: ServerState MUST be the last field. Because ServerState can
+ // contain null character (string termination of sererid string ..) it
+ // cannot be decoded using getNextLength() like the other fields. The
+ // only way is to rely on the end of the input buffer : and that forces
+ // the ServerState to be the last. This should be changed and we want to
+ // have more than one ServerState field.
serverState = new ServerState(in, pos, in.length - 1);
} catch (UnsupportedEncodingException e)
{
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
index fdce792..3f70856 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ReplicationMsg.java
@@ -70,6 +70,9 @@
static final byte MSG_TYPE_START_SESSION = 27;
static final byte MSG_TYPE_CHANGE_STATUS = 28;
static final byte MSG_TYPE_GENERIC_UPDATE = 29;
+ static final byte MSG_TYPE_START_ECL = 30;
+ static final byte MSG_TYPE_START_ECL_SESSION = 31;
+ static final byte MSG_TYPE_ECL_UPDATE = 32;
// Adding a new type of message here probably requires to
// change accordingly generateMsg method below
@@ -215,6 +218,15 @@
case MSG_TYPE_GENERIC_UPDATE:
msg = new UpdateMsg(buffer);
break;
+ case MSG_TYPE_START_ECL:
+ msg = new ServerStartECLMsg(buffer);
+ break;
+ case MSG_TYPE_START_ECL_SESSION:
+ msg = new StartECLSessionMsg(buffer);
+ break;
+ case MSG_TYPE_ECL_UPDATE:
+ msg = new ECLUpdateMsg(buffer);
+ break;
default:
throw new DataFormatException("received message with unknown type");
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java
new file mode 100644
index 0000000..366ff2d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartECLMsg.java
@@ -0,0 +1,377 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.protocol;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.zip.DataFormatException;
+
+import org.opends.server.replication.common.ServerState;
+
+/**
+ * This message is used by LDAP server when they first connect.
+ * to a replication server to let them know who they are and what is their state
+ * (their RUV)
+ */
+public class ServerStartECLMsg extends StartMsg
+{
+ private String serverURL;
+ private int maxReceiveQueue;
+ private int maxSendQueue;
+ private int maxReceiveDelay;
+ private int maxSendDelay;
+ private int windowSize;
+ private ServerState serverState = null;
+
+ /**
+ * The time in milliseconds between heartbeats from the replication
+ * server. Zero means heartbeats are off.
+ */
+ private long heartbeatInterval = 0;
+
+ /**
+ * Whether to continue using SSL to encrypt messages after the start
+ * messages have been exchanged.
+ */
+
+ private boolean sslEncryption;
+
+ /**
+ * Creates a new ServerStartMsg. This message is to be sent by an LDAP
+ * Server after being connected to a replication server for a given
+ * replication domain.
+ *
+ * @param baseDn The base DN.
+ * @param maxReceiveDelay The max receive delay for this server.
+ * @param maxReceiveQueue The max receive Queue for this server.
+ * @param maxSendDelay The max Send Delay from this server.
+ * @param maxSendQueue The max send Queue from this server.
+ * @param windowSize The window size used by this server.
+ * @param heartbeatInterval The requested heartbeat interval.
+ * @param serverState The state of this server.
+ * @param protocolVersion The replication protocol version of the creator.
+ * @param generationId The generationId for this server.
+ * @param sslEncryption Whether to continue using SSL to encrypt messages
+ * after the start messages have been exchanged.
+ * @param groupId The group id of the DS for this DN
+ */
+ public ServerStartECLMsg(String baseDn, int maxReceiveDelay,
+ int maxReceiveQueue, int maxSendDelay,
+ int maxSendQueue, int windowSize,
+ long heartbeatInterval,
+ ServerState serverState,
+ short protocolVersion,
+ long generationId,
+ boolean sslEncryption,
+ byte groupId)
+ {
+ super(protocolVersion, generationId);
+
+ this.maxReceiveDelay = maxReceiveDelay;
+ this.maxReceiveQueue = maxReceiveQueue;
+ this.maxSendDelay = maxSendDelay;
+ this.maxSendQueue = maxSendQueue;
+ this.windowSize = windowSize;
+ this.heartbeatInterval = heartbeatInterval;
+ this.sslEncryption = sslEncryption;
+ this.serverState = serverState;
+ this.groupId = groupId;
+
+ try
+ {
+ /* TODO : find a better way to get the server URL */
+ this.serverURL = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e)
+ {
+ this.serverURL = "Unknown host";
+ }
+ }
+
+ /**
+ * Creates a new ServerStartMsg from its encoded form.
+ *
+ * @param in The byte array containing the encoded form of the
+ * ServerStartMsg.
+ * @throws DataFormatException If the byte array does not contain a valid
+ * encoded form of the ServerStartMsg.
+ */
+ public ServerStartECLMsg(byte[] in) throws DataFormatException
+ {
+ byte[] allowedPduTypes = new byte[1];
+ allowedPduTypes[0] = MSG_TYPE_START_ECL;
+ headerLength = decodeHeader(allowedPduTypes, in);
+
+ try
+ {
+ /* first bytes are the header */
+ int pos = headerLength;
+
+ /*
+ * read the ServerURL
+ */
+ int length = getNextLength(in, pos);
+ serverURL = new String(in, pos, length, "UTF-8");
+ pos += length +1;
+
+ /*
+ * read the maxReceiveDelay
+ */
+ length = getNextLength(in, pos);
+ maxReceiveDelay = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ /*
+ * read the maxReceiveQueue
+ */
+ length = getNextLength(in, pos);
+ maxReceiveQueue = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ /*
+ * read the maxSendDelay
+ */
+ length = getNextLength(in, pos);
+ maxSendDelay = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ /*
+ * read the maxSendQueue
+ */
+ length = getNextLength(in, pos);
+ maxSendQueue = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ /*
+ * read the windowSize
+ */
+ length = getNextLength(in, pos);
+ windowSize = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ /*
+ * read the heartbeatInterval
+ */
+ length = getNextLength(in, pos);
+ heartbeatInterval = Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ /*
+ * read the sslEncryption setting
+ */
+ length = getNextLength(in, pos);
+ sslEncryption = Boolean.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ // Read the ServerState
+ // Caution: ServerState MUST be the last field. Because ServerState can
+ // contain null character (string termination of sererid string ..) it
+ // cannot be decoded using getNextLength() like the other fields. The
+ // only way is to rely on the end of the input buffer : and that forces
+ // the ServerState to be the last. This should be changed and we want to
+ // have more than one ServerState field.
+ serverState = new ServerState(in, pos, in.length - 1);
+
+ } catch (UnsupportedEncodingException e)
+ {
+ throw new DataFormatException("UTF-8 is not supported by this jvm.");
+ }
+ }
+
+ /**
+ * get the Server URL from the message.
+ * @return the server URL
+ */
+ public String getServerURL()
+ {
+ return serverURL;
+ }
+
+ /**
+ * Get the maxReceiveDelay.
+ * @return Returns the maxReceiveDelay.
+ */
+ public int getMaxReceiveDelay()
+ {
+ return maxReceiveDelay;
+ }
+
+ /**
+ * Get the maxReceiveQueue.
+ * @return Returns the maxReceiveQueue.
+ */
+ public int getMaxReceiveQueue()
+ {
+ return maxReceiveQueue;
+ }
+
+ /**
+ * Get the maxSendDelay.
+ * @return Returns the maxSendDelay.
+ */
+ public int getMaxSendDelay()
+ {
+ return maxSendDelay;
+ }
+
+ /**
+ * Get the maxSendQueue.
+ * @return Returns the maxSendQueue.
+ */
+ public int getMaxSendQueue()
+ {
+ return maxSendQueue;
+ }
+
+ /**
+ * Get the ServerState.
+ * @return The ServerState.
+ */
+ public ServerState getServerState()
+ {
+ return serverState;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getBytes()
+ {
+ try {
+ byte[] byteServerUrl = serverURL.getBytes("UTF-8");
+ byte[] byteMaxRecvDelay =
+ String.valueOf(maxReceiveDelay).getBytes("UTF-8");
+ byte[] byteMaxRecvQueue =
+ String.valueOf(maxReceiveQueue).getBytes("UTF-8");
+ byte[] byteMaxSendDelay =
+ String.valueOf(maxSendDelay).getBytes("UTF-8");
+ byte[] byteMaxSendQueue =
+ String.valueOf(maxSendQueue).getBytes("UTF-8");
+ byte[] byteWindowSize =
+ String.valueOf(windowSize).getBytes("UTF-8");
+ byte[] byteHeartbeatInterval =
+ String.valueOf(heartbeatInterval).getBytes("UTF-8");
+ byte[] byteSSLEncryption =
+ String.valueOf(sslEncryption).getBytes("UTF-8");
+ byte[] byteServerState = serverState.getBytes();
+
+ int length = byteServerUrl.length + 1 +
+ byteMaxRecvDelay.length + 1 +
+ byteMaxRecvQueue.length + 1 +
+ byteMaxSendDelay.length + 1 +
+ byteMaxSendQueue.length + 1 +
+ byteWindowSize.length + 1 +
+ byteHeartbeatInterval.length + 1 +
+ byteSSLEncryption.length + 1 +
+ byteServerState.length + 1;
+
+ /* encode the header in a byte[] large enough to also contain the mods */
+ byte resultByteArray[] = encodeHeader(MSG_TYPE_START_ECL, length);
+ int pos = headerLength;
+
+ pos = addByteArray(byteServerUrl, resultByteArray, pos);
+
+ pos = addByteArray(byteMaxRecvDelay, resultByteArray, pos);
+
+ pos = addByteArray(byteMaxRecvQueue, resultByteArray, pos);
+
+ pos = addByteArray(byteMaxSendDelay, resultByteArray, pos);
+
+ pos = addByteArray(byteMaxSendQueue, resultByteArray, pos);
+
+ pos = addByteArray(byteWindowSize, resultByteArray, pos);
+
+ pos = addByteArray(byteHeartbeatInterval, resultByteArray, pos);
+
+ pos = addByteArray(byteSSLEncryption, resultByteArray, pos);
+
+ pos = addByteArray(byteServerState, resultByteArray, pos);
+
+ return resultByteArray;
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Get the window size for the ldap server that created the message.
+ *
+ * @return The window size for the ldap server that created the message.
+ */
+ public int getWindowSize()
+ {
+ return windowSize;
+ }
+
+ /**
+ * Get the heartbeat interval requested by the ldap server that created the
+ * message.
+ *
+ * @return The heartbeat interval requested by the ldap server that created
+ * the message.
+ */
+ public long getHeartbeatInterval()
+ {
+ return heartbeatInterval;
+ }
+
+ /**
+ * Get the SSL encryption value for the ldap server that created the
+ * message.
+ *
+ * @return The SSL encryption value for the ldap server that created the
+ * message.
+ */
+ public boolean getSSLEncryption()
+ {
+ return sslEncryption;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return this.getClass().getCanonicalName() + " content: " +
+ "\nprotocolVersion: " + protocolVersion +
+ "\ngenerationId: " + generationId +
+ "\ngroupId: " + groupId +
+ "\nheartbeatInterval: " + heartbeatInterval +
+ "\nmaxReceiveDelay: " + maxReceiveDelay +
+ "\nmaxReceiveQueue: " + maxReceiveQueue +
+ "\nmaxSendDelay: " + maxSendDelay +
+ "\nmaxSendQueue: " + maxSendQueue +
+ "\nserverState: " + serverState +
+ "\nserverURL: " + serverURL +
+ "\nsslEncryption: " + sslEncryption +
+ "\nwindowSize: " + windowSize;
+ }
+ }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java
index f5679cc..33720f3 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/ServerStartMsg.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
+ * Copyright 2006-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.protocol;
@@ -208,9 +208,13 @@
sslEncryption = Boolean.valueOf(new String(in, pos, length, "UTF-8"));
pos += length +1;
- /*
- * read the ServerState
- */
+ // Read the ServerState
+ // Caution: ServerState MUST be the last field. Because ServerState can
+ // contain null character (string termination of sererid string ..) it
+ // cannot be decoded using getNextLength() like the other fields. The
+ // only way is to rely on the end of the input buffer : and that forces
+ // the ServerState to be the last. This should be changed and we want to
+ // have more than one ServerState field.
serverState = new ServerState(in, pos, in.length - 1);
} catch (UnsupportedEncodingException e)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java
new file mode 100644
index 0000000..b0bedc8
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/protocol/StartECLSessionMsg.java
@@ -0,0 +1,432 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.protocol;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.zip.DataFormatException;
+
+import org.opends.server.replication.common.ChangeNumber;
+
+
+/**
+ * This class specifies the parameters of a search request on the ECL.
+ * It is used as an interface between the requestor (plugin part)
+ * - either as an han
+ */
+public class StartECLSessionMsg extends ReplicationMsg
+{
+
+ /**
+ * This specifies that the ECL is requested from a provided cookie value
+ * defined as a MultiDomainServerState.
+ */
+ public final static short REQUEST_TYPE_FROM_COOKIE = 0;
+
+ /**
+ * This specifies that the ECL is requested from a provided interval
+ * of change numbers (as defined by draft-good-ldap-changelog [CHANGELOG]
+ * and NOT replication change numbers).
+ * TODO: not yet implemented
+ */
+ public final static short REQUEST_TYPE_FROM_DRAFT_CHANGE_NUMBER = 1;
+
+ /**
+ * This specifies that the ECL is requested ony for the entry that have
+ * a repl change number matching the provided one.
+ * TODO: not yet implemented
+ */
+ public final static short REQUEST_TYPE_EQUALS_REPL_CHANGE_NUMBER = 2;
+
+
+
+ /**
+ * This specifies that the request on the ECL is a PERSISTENT search
+ * with changesOnly = false.
+ */
+ public final static short PERSISTENT = 0;
+
+ /**
+ * This specifies that the request on the ECL is a NOT a PERSISTENT search.
+ */
+ public final static short NON_PERSISTENT = 1;
+
+ /**
+ * This specifies that the request on the ECL is a PERSISTENT search
+ * with changesOnly = false.
+ */
+ public final static short PERSISTENT_CHANGES_ONLY = 2;;
+
+
+
+ // The type of request as defined by REQUEST_TYPE_...
+ private short eclRequestType;
+
+ // When eclRequestType = FROM_COOKIE,
+ // specifies the provided cookie value.
+ private String crossDomainServerState = "";
+
+ // When eclRequestType = FROM_CHANGE_NUMBER,
+ // specifies the provided change number first and last - [CHANGELOG]
+ private int firstDraftChangeNumber = -1;
+ private int lastDraftChangeNumber = -1;
+
+ // When eclRequestType = EQUALS_REPL_CHANGE_NUMBER,
+ // specifies the provided replication change number.
+ private ChangeNumber changeNumber;
+
+ // Specifies whether the search is persistent and changesOnly
+ private short isPersistent;
+
+ // A string helping debuging and tracing the client operation related when
+ // processing, on the RS side, a request on the ECL.
+ private String operationId = "";
+
+ // Excluded domains
+ private ArrayList<String> excludedServiceIDs = new ArrayList<String>();
+
+ /**
+ * Creates a new StartSessionMsg message from its encoded form.
+ *
+ * @param in The byte array containing the encoded form of the message.
+ * @throws java.util.zip.DataFormatException If the byte array does not
+ * contain a valid encoded form of the message.
+ */
+ public StartECLSessionMsg(byte[] in) throws DataFormatException
+ {
+ /*
+ * The message is stored in the form:
+ * <message type><status><assured flag><assured mode><safe data level>
+ * <list of referrals urls>
+ * (each referral url terminates with 0)
+ */
+
+ try
+ {
+ /* first bytes are the header */
+ int pos = 0;
+
+ /* first byte is the type */
+ if (in.length < 1 || in[pos++] != MSG_TYPE_START_ECL_SESSION)
+ {
+ throw new DataFormatException(
+ "Input is not a valid " + this.getClass().getCanonicalName());
+ }
+
+ // start mode
+ int length = getNextLength(in, pos);
+ eclRequestType = Short.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ // sequenceNumber
+ length = getNextLength(in, pos);
+ firstDraftChangeNumber =
+ Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ // stopSequenceNumber
+ length = getNextLength(in, pos);
+ lastDraftChangeNumber =
+ Integer.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length +1;
+
+ // replication changeNumber
+ length = getNextLength(in, pos);
+ String changenumberStr = new String(in, pos, length, "UTF-8");
+ pos += length + 1;
+ changeNumber = new ChangeNumber(changenumberStr);
+
+ // persistentSearch mode
+ length = getNextLength(in, pos);
+ isPersistent = Short.valueOf(new String(in, pos, length, "UTF-8"));
+ pos += length + 1;
+
+ // generalized state
+ length = getNextLength(in, pos);
+ crossDomainServerState = new String(in, pos, length, "UTF-8");
+ pos += length + 1;
+
+ // operation id
+ length = getNextLength(in, pos);
+ operationId = new String(in, pos, length, "UTF-8");
+ pos += length + 1;
+
+ // excluded DN
+ length = getNextLength(in, pos);
+ String excludedDNsString = new String(in, pos, length, "UTF-8");
+ if (excludedDNsString.length()>0)
+ {
+ String[] excludedDNsStr = excludedDNsString.split(";");
+ for (String excludedDNStr : excludedDNsStr)
+ {
+ this.excludedServiceIDs.add(excludedDNStr);
+ }
+ }
+ pos += length + 1;
+
+ } catch (UnsupportedEncodingException e)
+ {
+ throw new DataFormatException("UTF-8 is not supported by this jvm.");
+ } catch (IllegalArgumentException e)
+ {
+ throw new DataFormatException(e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a new StartSessionMsg message with the given required parameters.
+ */
+ public StartECLSessionMsg()
+ {
+ changeNumber = new ChangeNumber((short)0,0,(short)0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getBytes()
+ {
+ String excludedSIDsString = new String();
+ for (String excludedServiceID : excludedServiceIDs)
+ {
+ excludedSIDsString = excludedSIDsString.concat(excludedServiceID+";");
+ }
+
+ try
+ {
+ byte[] byteMode =
+ Short.toString(eclRequestType).getBytes("UTF-8");
+ byte[] byteSequenceNumber =
+ String.valueOf(firstDraftChangeNumber).getBytes("UTF-8");
+ byte[] byteStopSequenceNumber =
+ String.valueOf(lastDraftChangeNumber).getBytes("UTF-8");
+ byte[] byteChangeNumber =
+ changeNumber.toString().getBytes("UTF-8");
+ byte[] bytePsearch =
+ Short.toString(isPersistent).getBytes();
+ byte[] byteGeneralizedState =
+ String.valueOf(crossDomainServerState).getBytes("UTF-8");
+ byte[] byteOperationId =
+ String.valueOf(operationId).getBytes("UTF-8");
+ byte[] byteExcludedDNs =
+ String.valueOf(excludedSIDsString).getBytes("UTF-8");
+
+ int length =
+ byteMode.length + 1 +
+ byteSequenceNumber.length + 1 +
+ byteStopSequenceNumber.length + 1 +
+ byteChangeNumber.length + 1 +
+ bytePsearch.length + 1 +
+ byteGeneralizedState.length + 1 +
+ byteOperationId.length + 1 +
+ byteExcludedDNs.length + 1 +
+ 1;
+
+ byte[] resultByteArray = new byte[length];
+ int pos = 0;
+ resultByteArray[pos++] = MSG_TYPE_START_ECL_SESSION;
+ pos = addByteArray(byteMode, resultByteArray, pos);
+ pos = addByteArray(byteSequenceNumber, resultByteArray, pos);
+ pos = addByteArray(byteStopSequenceNumber, resultByteArray, pos);
+ pos = addByteArray(byteChangeNumber, resultByteArray, pos);
+ pos = addByteArray(bytePsearch, resultByteArray, pos);
+ pos = addByteArray(byteGeneralizedState, resultByteArray, pos);
+ pos = addByteArray(byteOperationId, resultByteArray, pos);
+ pos = addByteArray(byteExcludedDNs, resultByteArray, pos);
+ return resultByteArray;
+
+ } catch (IOException e)
+ {
+ // never happens
+ return null;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return (this.getClass().getCanonicalName() + " [" +
+ " requestType="+ eclRequestType +
+ " persistentSearch=" + isPersistent +
+ " changeNumber=" + changeNumber +
+ " firstDraftChangeNumber=" + firstDraftChangeNumber +
+ " lastDraftChangeNumber=" + lastDraftChangeNumber +
+ " generalizedState=" + crossDomainServerState +
+ " operationId=" + operationId +
+ " excludedDNs=" + excludedServiceIDs + "]");
+ }
+
+ /**
+ * Getter on the changer number start.
+ * @return the changer number start.
+ */
+ public int getFirstDraftChangeNumber()
+ {
+ return firstDraftChangeNumber;
+ }
+
+ /**
+ * Getter on the changer number stop.
+ * @return the change number stop.
+ */
+ public int getLastDraftChangeNumber()
+ {
+ return lastDraftChangeNumber;
+ }
+
+ /**
+ * Setter on the first changer number (as defined by [CHANGELOG]).
+ * @param firstDraftChangeNumber the provided first change number.
+ */
+ public void setFirstDraftChangeNumber(int firstDraftChangeNumber)
+ {
+ this.firstDraftChangeNumber = firstDraftChangeNumber;
+ }
+
+ /**
+ * Setter on the last changer number (as defined by [CHANGELOG]).
+ * @param lastDraftChangeNumber the provided last change number.
+ */
+ public void setLastDraftChangeNumber(int lastDraftChangeNumber)
+ {
+ this.lastDraftChangeNumber = lastDraftChangeNumber;
+ }
+
+ /**
+ * Getter on the replication change number.
+ * @return the replication change number.
+ */
+ public ChangeNumber getChangeNumber()
+ {
+ return changeNumber;
+ }
+
+ /**
+ * Setter on the replication change number.
+ * @param changeNumber the provided replication change number.
+ */
+ public void setChangeNumber(ChangeNumber changeNumber)
+ {
+ this.changeNumber = changeNumber;
+ }
+ /**
+ * Getter on the type of request.
+ * @return the type of request.
+ */
+ public short getECLRequestType()
+ {
+ return eclRequestType;
+ }
+
+ /**
+ * Setter on the type of request.
+ * @param eclRequestType the provided type of request.
+ */
+ public void setECLRequestType(short eclRequestType)
+ {
+ this.eclRequestType = eclRequestType;
+ }
+
+ /**
+ * Getter on the persistent property of the search request on the ECL.
+ * @return the persistent property.
+ */
+ public short isPersistent()
+ {
+ return this.isPersistent;
+ }
+
+ /**
+ * Setter on the persistent property of the search request on the ECL.
+ * @param isPersistent the provided persistent property.
+ */
+ public void setPersistent(short isPersistent)
+ {
+ this.isPersistent = isPersistent;
+ }
+
+ /**
+ * Getter of the cross domain server state.
+ * @return the cross domain server state.
+ */
+ public String getCrossDomainServerState()
+ {
+ return this.crossDomainServerState;
+ }
+
+ /**
+ * Setter of the cross domain server state.
+ * @param crossDomainServerState the provided cross domain server state.
+ */
+ public void setCrossDomainServerState(String crossDomainServerState)
+ {
+ this.crossDomainServerState = crossDomainServerState;
+ }
+
+ /**
+ * Setter of the operation id.
+ * @param operationId The provided opration id.
+ */
+ public void setOperationId(String operationId)
+ {
+ this.operationId = operationId;
+ }
+
+ /**
+ * Getter on the operation id.
+ * @return the operation id.
+ */
+ public String getOperationId()
+ {
+ return this.operationId;
+ }
+
+ /**
+ * Getter on the list of excluded ServiceIDs.
+ * @return the list of excluded ServiceIDs.
+ */
+ public ArrayList<String> getExcludedServiceIDs()
+ {
+ return this.excludedServiceIDs;
+ }
+
+ /**
+ * Setter on the list of excluded ServiceIDs.
+ * @param excludedServiceIDs the provided list of excluded ServiceIDs.
+ */
+ public void setExcludedDNs(ArrayList<String> excludedServiceIDs)
+ {
+ this.excludedServiceIDs = excludedServiceIDs;
+ }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java
new file mode 100644
index 0000000..c5b6b1d
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/DataServerHandler.java
@@ -0,0 +1,724 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.replication.common.StatusMachine.*;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+
+import org.opends.messages.Message;
+import org.opends.server.replication.common.AssuredMode;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.common.StatusMachine;
+import org.opends.server.replication.common.StatusMachineEvent;
+import org.opends.server.replication.protocol.*;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeBuilder;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This class defines a server handler, which handles all interaction with a
+ * peer server (RS or DS).
+ */
+public class DataServerHandler extends ServerHandler
+{
+ // Status of this DS (only used if this server handler represents a DS)
+ private ServerStatus status = ServerStatus.INVALID_STATUS;
+
+ // Referrals URLs this DS is exporting
+ private List<String> refUrls = new ArrayList<String>();
+ // Assured replication enabled on DS or not
+ private boolean assuredFlag = false;
+ // DS assured mode (relevant if assured replication enabled)
+ private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
+ // DS safe data level (relevant if assured mode is safe data)
+ private byte safeDataLevel = (byte) -1;
+
+ /**
+ * Creates a new data server handler.
+ * @param session The session opened with the remote data server.
+ * @param queueSize The queue size.
+ * @param replicationServerURL The URL of the hosting RS.
+ * @param replicationServerId The serverID of the hosting RS.
+ * @param replicationServer The hosting RS.
+ * @param rcvWindowSize The receiving window size.
+ */
+ public DataServerHandler(
+ ProtocolSession session,
+ int queueSize,
+ String replicationServerURL,
+ short replicationServerId,
+ ReplicationServer replicationServer,
+ int rcvWindowSize)
+ {
+ super(session, queueSize, replicationServerURL, replicationServerId,
+ replicationServer, rcvWindowSize);
+ }
+
+ /**
+ * Order the peer DS server to change his status or close the connection
+ * according to the requested new generation id.
+ * @param newGenId The new generation id to take into account
+ * @throws IOException If IO error occurred.
+ */
+ public void changeStatusForResetGenId(long newGenId)
+ throws IOException
+ {
+ StatusMachineEvent event = null;
+
+ if (newGenId == -1)
+ {
+ // The generation id is being made invalid, let's put the DS
+ // into BAD_GEN_ID_STATUS
+ event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
+ } else
+ {
+ if (newGenId == generationId)
+ {
+ if (status == ServerStatus.BAD_GEN_ID_STATUS)
+ {
+ // This server has the good new reference generation id.
+ // Close connection with him to force his reconnection: DS will
+ // reconnect in NORMAL_STATUS or DEGRADED_STATUS.
+
+ if (debugEnabled())
+ {
+ TRACER.debugInfo(
+ "In RS " +
+ replicationServerDomain.getReplicationServer().getServerId() +
+ ". Closing connection to DS " + getServerId() +
+ " for baseDn " + getServiceId() +
+ " to force reconnection as new local" +
+ " generationId and remote one match and DS is in bad gen id: " +
+ newGenId);
+ }
+
+ // Connection closure must not be done calling RSD.stopHandler() as it
+ // would rewait the RSD lock that we already must have entering this
+ // method. This would lead to a reentrant lock which we do not want.
+ // So simply close the session, this will make the hang up appear
+ // after the reader thread that took the RSD lock realeases it.
+ try
+ {
+ if (session != null)
+ session.close();
+ } catch (IOException e)
+ {
+ // ignore
+ }
+
+ // NOT_CONNECTED_STATUS is the last one in RS session life: handler
+ // will soon disappear after this method call...
+ status = ServerStatus.NOT_CONNECTED_STATUS;
+ return;
+ } else
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugInfo(
+ "In RS " +
+ replicationServerDomain.getReplicationServer().getServerId() +
+ ". DS " + getServerId() + " for baseDn " + getServiceId() +
+ " has already generation id " + newGenId +
+ " so no ChangeStatusMsg sent to him.");
+ }
+ return;
+ }
+ } else
+ {
+ // This server has a bad generation id compared to new reference one,
+ // let's put it into BAD_GEN_ID_STATUS
+ event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
+ }
+ }
+
+ if ((event == StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT) &&
+ (status == ServerStatus.FULL_UPDATE_STATUS))
+ {
+ // Prevent useless error message (full update status cannot lead to bad
+ // gen status)
+ Message message = NOTE_BAD_GEN_ID_IN_FULL_UPDATE.get(
+ Short.toString(replicationServerDomain.
+ getReplicationServer().getServerId()),
+ getServiceId().toString(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(newGenId));
+ logError(message);
+ return;
+ }
+
+ ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
+
+ if (newStatus == ServerStatus.INVALID_STATUS)
+ {
+ Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(getServiceId().toString(),
+ Short.toString(serverId), status.toString(), event.toString());
+ logError(msg);
+ return;
+ }
+
+ // Send message requesting to change the DS status
+ ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
+ ServerStatus.INVALID_STATUS);
+
+ if (debugEnabled())
+ {
+ TRACER.debugInfo(
+ "In RS " +
+ replicationServerDomain.getReplicationServer().getServerId() +
+ " Sending change status for reset gen id to " + getServerId() +
+ " for baseDn " + getServiceId() + ":\n" + csMsg);
+ }
+
+ session.publish(csMsg);
+
+ status = newStatus;
+ }
+
+ /**
+ * Change the status according to the event generated from the status
+ * analyzer.
+ * @param event The event to be used for new status computation
+ * @return The new status of the DS
+ * @throws IOException When raised by the underlying session
+ */
+ public ServerStatus changeStatusFromStatusAnalyzer(StatusMachineEvent event)
+ throws IOException
+ {
+ // Check state machine allows this new status (Sanity check)
+ ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
+ if (newStatus == ServerStatus.INVALID_STATUS)
+ {
+ Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(getServiceId().toString(),
+ Short.toString(serverId), status.toString(), event.toString());
+ logError(msg);
+ // Status analyzer must only change from NORMAL_STATUS to DEGRADED_STATUS
+ // and vice versa. We may are being trying to change the status while for
+ // instance another status has just been entered: e.g a full update has
+ // just been engaged. In that case, just ignore attempt to change the
+ // status
+ return newStatus;
+ }
+
+ // Send message requesting to change the DS status
+ ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
+ ServerStatus.INVALID_STATUS);
+
+ if (debugEnabled())
+ {
+ TRACER.debugInfo(
+ "In RS " +
+ replicationServerDomain.getReplicationServer().getServerId() +
+ " Sending change status from status analyzer to " + getServerId() +
+ " for baseDn " + getServiceId() + ":\n" + csMsg);
+ }
+
+ session.publish(csMsg);
+
+ status = newStatus;
+
+ return newStatus;
+ }
+
+ private void createStatusAnalyzer()
+ {
+ if (!replicationServerDomain.isRunningStatusAnalyzer())
+ {
+ replicationServerDomain.startStatusAnalyzer();
+ }
+ }
+
+ /**
+ * Retrieves a set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is requested.
+ *
+ * @return A set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is
+ * requested.
+ */
+ @Override
+ public ArrayList<Attribute> getMonitorData()
+ {
+ // Get the generic ones
+ ArrayList<Attribute> attributes = super.getMonitorData();
+
+ // Add the specific DS ones
+ attributes.add(Attributes.create("replica", serverURL));
+ attributes.add(Attributes.create("connected-to",
+ this.replicationServerDomain.getReplicationServer()
+ .getMonitorInstanceName()));
+
+ try
+ {
+ MonitorData md;
+ md = replicationServerDomain.computeMonitorData();
+
+ // Oldest missing update
+ Long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId);
+ if ((approxFirstMissingDate != null) && (approxFirstMissingDate > 0))
+ {
+ Date date = new Date(approxFirstMissingDate);
+ attributes.add(Attributes.create(
+ "approx-older-change-not-synchronized", date.toString()));
+ attributes.add(Attributes.create(
+ "approx-older-change-not-synchronized-millis", String
+ .valueOf(approxFirstMissingDate)));
+ }
+
+ // Missing changes
+ long missingChanges = md.getMissingChanges(serverId);
+ attributes.add(Attributes.create("missing-changes", String
+ .valueOf(missingChanges)));
+
+ // Replication delay
+ long delay = md.getApproxDelay(serverId);
+ attributes.add(Attributes.create("approximate-delay", String
+ .valueOf(delay)));
+
+ /* get the Server State */
+ AttributeBuilder builder = new AttributeBuilder("server-state");
+ ServerState state = md.getLDAPServerState(serverId);
+ if (state != null)
+ {
+ for (String str : state.toStringSet())
+ {
+ builder.add(str);
+ }
+ attributes.add(builder.toAttribute());
+ }
+
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
+ // We failed retrieving the monitor data.
+ attributes.add(Attributes.create("error", message.toString()));
+ }
+ return attributes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getMonitorInstanceName()
+ {
+ String str = serverURL + " " + String.valueOf(serverId);
+
+ return "Connected Replica " + str +
+ ",cn=" + replicationServerDomain.getMonitorInstanceName();
+ }
+
+ /**
+ * Gets the status of the connected DS.
+ * @return The status of the connected DS.
+ */
+ public ServerStatus getStatus()
+ {
+ return status;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isDataServer()
+ {
+ return true;
+ }
+
+ /**
+ * Process message of a remote server changing his status.
+ * @param csMsg The message containing the new status
+ * @return The new server status of the DS
+ */
+ public ServerStatus processNewStatus(ChangeStatusMsg csMsg)
+ {
+ // Get the status the DS just entered
+ ServerStatus reqStatus = csMsg.getNewStatus();
+ // Translate new status to a state machine event
+ StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus);
+ if (event == StatusMachineEvent.INVALID_EVENT)
+ {
+ Message msg = ERR_RS_INVALID_NEW_STATUS.get(reqStatus.toString(),
+ getServiceId().toString(), Short.toString(serverId));
+ logError(msg);
+ return ServerStatus.INVALID_STATUS;
+ }
+
+ // Check state machine allows this new status
+ ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
+ if (newStatus == ServerStatus.INVALID_STATUS)
+ {
+ Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(getServiceId().toString(),
+ Short.toString(serverId), status.toString(), event.toString());
+ logError(msg);
+ return ServerStatus.INVALID_STATUS;
+ }
+
+ status = newStatus;
+
+ return status;
+ }
+
+ /**
+ * Processes a start message received from a remote data server.
+ * @param serverStartMsg The provided start message received.
+ * @return flag specifying whether the remote server requests encryption.
+ * @throws DirectoryException raised when an error occurs.
+ */
+ public boolean processStartFromRemote(ServerStartMsg serverStartMsg)
+ throws DirectoryException
+ {
+ generationId = serverStartMsg.getGenerationId();
+ protocolVersion = ProtocolVersion.minWithCurrent(
+ serverStartMsg.getVersion());
+ serverId = serverStartMsg.getServerId();
+ serverURL = serverStartMsg.getServerURL();
+ groupId = serverStartMsg.getGroupId();
+ maxReceiveDelay = serverStartMsg.getMaxReceiveDelay();
+ maxReceiveQueue = serverStartMsg.getMaxReceiveQueue();
+ maxSendDelay = serverStartMsg.getMaxSendDelay();
+ maxSendQueue = serverStartMsg.getMaxSendQueue();
+ heartbeatInterval = serverStartMsg.getHeartbeatInterval();
+
+ // generic stuff
+ setServiceIdAndDomain(serverStartMsg.getBaseDn());
+ setInitialServerState(serverStartMsg.getServerState());
+ setSendWindowSize(serverStartMsg.getWindowSize());
+
+ if (maxReceiveQueue > 0)
+ restartReceiveQueue = (maxReceiveQueue > 1000 ? maxReceiveQueue -
+ 200 : maxReceiveQueue * 8 / 10);
+ else
+ restartReceiveQueue = 0;
+
+ if (maxSendQueue > 0)
+ restartSendQueue =
+ (maxSendQueue > 1000 ? maxSendQueue - 200 : maxSendQueue * 8 /
+ 10);
+ else
+ restartSendQueue = 0;
+
+ if (maxReceiveDelay > 0)
+ restartReceiveDelay = (maxReceiveDelay > 10 ? maxReceiveDelay - 1
+ : maxReceiveDelay);
+ else
+ restartReceiveDelay = 0;
+
+ if (maxSendDelay > 0)
+ restartSendDelay =
+ (maxSendDelay > 10 ? maxSendDelay - 1 : maxSendDelay);
+ else
+ restartSendDelay = 0;
+
+ if (heartbeatInterval < 0)
+ {
+ heartbeatInterval = 0;
+ }
+ return serverStartMsg.getSSLEncryption();
+ }
+
+ /**
+ * Registers this handler into its related domain and notifies the domain
+ * about the new DS.
+ */
+ public void registerIntoDomain()
+ {
+ // Alright, connected with new DS: store handler.
+ Map<Short, DataServerHandler> connectedDSs =
+ replicationServerDomain.getConnectedDSs();
+ connectedDSs.put(serverId, this);
+
+ // Tell peer DSs a new DS just connected to us
+ // No need to resend topo msg to this just new DS so not null
+ // argument
+ replicationServerDomain.buildAndSendTopoInfoToDSs(this);
+ // Tell peer RSs a new DS just connected to us
+ replicationServerDomain.buildAndSendTopoInfoToRSs();
+ }
+
+ // Send our own TopologyMsg to DS
+ private TopologyMsg sendTopoToRemoteDS()
+ throws IOException
+ {
+ TopologyMsg outTopoMsg = replicationServerDomain.createTopologyMsgForDS(
+ this.serverId);
+ session.publish(outTopoMsg);
+ return outTopoMsg;
+ }
+ /**
+ * Starts the handler from a remote ServerStart message received from
+ * the remote data server.
+ * @param inServerStartMsg The provided ServerStart message received.
+ */
+ public void startFromRemoteDS(ServerStartMsg inServerStartMsg)
+ {
+ try
+ {
+ // initializations
+ localGenerationId = -1;
+ oldGenerationId = -100;
+
+ // processes the ServerStart message received
+ boolean sessionInitiatorSSLEncryption =
+ processStartFromRemote(inServerStartMsg);
+
+ // Get or Create the ReplicationServerDomain
+ replicationServerDomain = getDomain(true);
+ localGenerationId = replicationServerDomain.getGenerationId();
+ oldGenerationId = localGenerationId;
+
+ // Hack to be sure that if a server disconnects and reconnect, we
+ // let the reader thread see the closure and cleanup any reference
+ // to old connection
+ replicationServerDomain.
+ waitDisconnection(inServerStartMsg.getServerId());
+
+ // Duplicate server ?
+ if (!replicationServerDomain.checkForDuplicateDS(this))
+ {
+ abortStart(null);
+ return;
+ }
+
+ // lock with no timeout
+ lockDomain(false);
+
+ //
+ ReplServerStartMsg outReplServerStartMsg = null;
+ try
+ {
+ outReplServerStartMsg = sendStartToRemote((short)-1);
+
+ // log
+ logStartHandshakeRCVandSND(inServerStartMsg, outReplServerStartMsg);
+
+ // The session initiator decides whether to use SSL.
+ // Until here session is encrypted then it depends on the negociation
+ if (!sessionInitiatorSSLEncryption)
+ session.stopEncryption();
+
+ // wait and process StartSessionMsg from remote RS
+ StartSessionMsg inStartSessionMsg =
+ waitAndProcessStartSessionFromRemoteDS();
+
+ // Send our own TopologyMsg to remote RS
+ TopologyMsg outTopoMsg = sendTopoToRemoteDS();
+
+ logStartSessionHandshake(inStartSessionMsg, outTopoMsg);
+ }
+ catch(IOException e)
+ {
+ // We do not want polluting error log if error is due to normal session
+ // aborted after handshake phase one from a DS that is searching for
+ // best suitable RS.
+
+ // don't log a poluting error when connection aborted
+ // from a DS that wanted only to perform handshake phase 1 in order
+ // to determine the best suitable RS:
+ // 1) -> ServerStartMsg
+ // 2) <- ReplServerStartMsg
+ // 3) connection closure
+
+ throw new DirectoryException(ResultCode.OTHER, null, null);
+ }
+ catch (NotSupportedOldVersionPDUException e)
+ {
+ // We do not need to support DS V1 connection, we just accept RS V1
+ // connection:
+ // We just trash the message, log the event for debug purpose and close
+ // the connection
+ throw new DirectoryException(ResultCode.OTHER, null, null);
+ }
+ catch (Exception e)
+ {
+ // We do not need to support DS V1 connection, we just accept RS V1
+ // connection:
+ // We just trash the message, log the event for debug purpose and close
+ // the connection
+ throw new DirectoryException(ResultCode.OTHER, null, null);
+ }
+
+ // Create the status analyzer for the domain if not already started
+ createStatusAnalyzer();
+
+ registerIntoDomain();
+
+ super.finalizeStart();
+
+ }
+ catch(DirectoryException de)
+ {
+ abortStart(de.getMessageObject());
+ }
+ catch(Exception e)
+ {
+ abortStart(null);
+ }
+ finally
+ {
+ if ((replicationServerDomain != null) &&
+ replicationServerDomain.hasLock())
+ replicationServerDomain.release();
+ }
+ }
+ /**
+ * Creates a DSInfo structure representing this remote DS.
+ * @return The DSInfo structure representing this remote DS
+ */
+ public DSInfo toDSInfo()
+ {
+ DSInfo dsInfo = new DSInfo(serverId, replicationServerId, generationId,
+ status, assuredFlag, assuredMode, safeDataLevel, groupId, refUrls);
+
+ return dsInfo;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ String localString;
+ if (serverId != 0)
+ {
+ localString = "Directory Server ";
+ localString += serverId + " " + serverURL + " " + getServiceId();
+ } else
+ localString = "Unknown server";
+
+ return localString;
+ }
+
+ /**
+ * Wait receiving the StartSessionMsg from the remote DS and process it.
+ * @return the startSessionMsg received
+ * @throws DirectoryException
+ * @throws IOException
+ * @throws ClassNotFoundException
+ * @throws DataFormatException
+ * @throws NotSupportedOldVersionPDUException
+ */
+ private StartSessionMsg waitAndProcessStartSessionFromRemoteDS()
+ throws DirectoryException, IOException, ClassNotFoundException,
+ DataFormatException,
+ NotSupportedOldVersionPDUException
+ {
+ ReplicationMsg msg = null;
+ msg = session.receive();
+
+ if (!(msg instanceof StartSessionMsg))
+ {
+ Message message = Message.raw(
+ "Protocol error: StartSessionMsg required." + msg + " received.");
+ abortStart(message);
+ }
+
+ // Process StartSessionMsg sent by remote DS
+ StartSessionMsg startSessionMsg = (StartSessionMsg) msg;
+
+ this.status = startSessionMsg.getStatus();
+ // Sanity check: is it a valid initial status?
+ if (!isValidInitialStatus(this.status))
+ {
+ Message message = ERR_RS_INVALID_INIT_STATUS.get(
+ this.status.toString(),
+ getServiceId().toString(),
+ Short.toString(serverId));
+ throw new DirectoryException(ResultCode.OTHER, message);
+ }
+ this.refUrls = startSessionMsg.getReferralsURLs();
+ this.assuredFlag = startSessionMsg.isAssured();
+ this.assuredMode = startSessionMsg.getAssuredMode();
+ this.safeDataLevel = startSessionMsg.getSafeDataLevel();
+
+ /*
+ * If we have already a generationID set for the domain
+ * then
+ * if the connecting replica has not the same
+ * then it is degraded locally and notified by an error message
+ * else
+ * we set the generationID from the one received
+ * (unsaved yet on disk . will be set with the 1rst change
+ * received)
+ */
+ if (localGenerationId > 0)
+ {
+ if (generationId != localGenerationId)
+ {
+ Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(localGenerationId));
+ logError(message);
+ }
+ }
+ else
+ {
+ // We are an empty Replicationserver
+ if ((generationId > 0) && (!getServerState().isEmpty()))
+ {
+ // If the LDAP server has already sent changes
+ // it is not expected to connect to an empty RS
+ Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(localGenerationId));
+ logError(message);
+ }
+ else
+ {
+ // The local RS is not initialized - take the one received
+ // WARNING: Must be done before computing topo message to send
+ // to peer server as topo message must embed valid generation id
+ // for our server
+ oldGenerationId =
+ replicationServerDomain.setGenerationId(generationId, false);
+ }
+ }
+ return startSessionMsg;
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
new file mode 100644
index 0000000..ec855de
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerHandler.java
@@ -0,0 +1,1551 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.DataFormatException;
+
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.Severity;
+import org.opends.server.api.DirectoryThread;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.protocol.*;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
+
+/**
+ * This class defines a server handler, which handles all interaction with a
+ * peer replication server.
+ */
+public class ECLServerHandler extends ServerHandler
+{
+
+ // Properties filled only if remote server is a RS
+ private String serverAddressURL;
+
+ String operationId;
+
+ /**
+ * CLDomainContext : contains the state properties for the search
+ * currently being processed, by replication domain.
+ */
+ private class CLDomainContext
+ {
+ ReplicationServerDomain rsd; // the repl server domain
+ boolean active; // is the domain still active
+ MessageHandler mh; // the message handler associated
+ UpdateMsg nextMsg;
+ UpdateMsg nonElligiblemsg;
+ ServerState startState;
+ ServerState currentState;
+ ServerState stopState;
+
+ /**
+ * Add to the provider buffer a string representation of this object.
+ */
+ public void toString(StringBuilder buffer, int i)
+ {
+ CLDomainContext xx = clDomCtxts[i];
+ buffer.append(
+ " clDomCtxts(" + i + ") [act=" + xx.active +
+ " rsd=" + rsd +
+ " nextMsg=" + nextMsg + "(" +
+ (nextMsg != null?
+ new Date(nextMsg.getChangeNumber().getTime()).toString():"")
+ + ")" +
+ " nextNonEligibleMsg=" + nonElligiblemsg +
+ " startState=" + startState +
+ " stopState= " + stopState +
+ " currState= " + currentState + "]");
+ }
+ }
+
+ // The list of contexts by domain for the current search
+ CLDomainContext[] clDomCtxts = new CLDomainContext[0];
+
+ private void clDomCtxtsToString(String msg)
+ {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(msg+"\n");
+ for (int i=0;i<clDomCtxts.length;i++)
+ {
+ clDomCtxts[i].toString(buffer, i);
+ buffer.append("\n");
+ }
+ TRACER.debugInfo(
+ "In " + this.getName() + " clDomCtxts: " + buffer.toString());
+ }
+
+ /**
+ * Class that manages the state variables for the current search on the ECL.
+ */
+ private class CLTraverseCtxt
+ {
+ /**
+ * Specifies the next changer number (seqnum), -1 when not.
+ */
+ public int nextSeqnum;
+ /**
+ * Specifies whether the current search has been requested to be persistent
+ * or not.
+ */
+ public short isPersistent;
+ /**
+ * Specifies the last changer number (seqnum) requested.
+ */
+ public int stopSeqnum;
+ /**
+ * Specifies whether the change number (seqnum) db has been read until
+ * its end.
+ */
+ public boolean endOfSeqnumdbReached = false;
+ /**
+ * Specifies the current search phase.
+ * 1 = init
+ * 2 = persistent
+ */
+ public int searchPhase = 1;
+ /**
+ * Specifies the cookie contained in the request, specifying where
+ * to start serving the ECL.
+ */
+ public String generalizedStartState;
+ /**
+ * Specifies the current cookie value.
+ */
+ public MultiDomainServerState currentCookie =
+ new MultiDomainServerState();
+ /**
+ * Specifies the excluded DNs.
+ */
+ public ArrayList<String> excludedServiceIDs = new ArrayList<String>();
+
+ /**
+ * Provides a string representation of this object.
+ * @return the string representation.
+ */
+ public String toString()
+ {
+ return new String(
+ this.getClass().getCanonicalName() +
+ ":[" +
+ " nextSeqnum=" + nextSeqnum +
+ " persistent=" + isPersistent +
+ " stopSeqnum" + stopSeqnum +
+ " endOfSeqnumdbReached=" + endOfSeqnumdbReached +
+ " searchPhase=" + searchPhase +
+ " generalizedStartState=" + generalizedStartState +
+ "]");
+ }
+
+ }
+
+ // The context of the current search
+ private CLTraverseCtxt cLSearchCtxt = new CLTraverseCtxt();
+
+ /**
+ * Starts this handler based on a start message received from remote server.
+ * @param inECLStartMsg The start msg provided by the remote server.
+ * @return Whether the remote server requires encryption or not.
+ * @throws DirectoryException When a problem occurs.
+ */
+ public boolean processStartFromRemote(ServerStartECLMsg inECLStartMsg)
+ throws DirectoryException
+ {
+ try
+ {
+ protocolVersion = ProtocolVersion.minWithCurrent(
+ inECLStartMsg.getVersion());
+ generationId = inECLStartMsg.getGenerationId();
+ serverURL = inECLStartMsg.getServerURL();
+ int separator = serverURL.lastIndexOf(':');
+ serverAddressURL =
+ session.getRemoteAddress() + ":" + serverURL.substring(separator +
+ 1);
+ setInitialServerState(inECLStartMsg.getServerState());
+ setSendWindowSize(inECLStartMsg.getWindowSize());
+ if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+ {
+ // We support connection from a V1 RS
+ // Only V2 protocol has the group id in repl server start message
+ this.groupId = inECLStartMsg.getGroupId();
+ }
+ // FIXME:ECL Any generationID must be removed, it makes no sense here.
+ oldGenerationId = -100;
+ }
+ catch(Exception e)
+ {
+ Message message = Message.raw(e.getLocalizedMessage());
+ throw new DirectoryException(ResultCode.OTHER, message);
+ }
+ return inECLStartMsg.getSSLEncryption();
+ }
+
+ /**
+ * Creates a new handler object to a remote replication server.
+ * @param session The session with the remote RS.
+ * @param queueSize The queue size to manage updates to that RS.
+ * @param replicationServerURL The hosting local RS URL.
+ * @param replicationServerId The hosting local RS serverId.
+ * @param replicationServer The hosting local RS object.
+ * @param rcvWindowSize The receiving window size.
+ */
+ public ECLServerHandler(
+ ProtocolSession session,
+ int queueSize,
+ String replicationServerURL,
+ short replicationServerId,
+ ReplicationServer replicationServer,
+ int rcvWindowSize)
+ {
+ super(session, queueSize, replicationServerURL, replicationServerId,
+ replicationServer, rcvWindowSize);
+ try
+ {
+ setServiceIdAndDomain("cn=changelog");
+ }
+ catch(DirectoryException de)
+ {
+ // no chance to have a bad domain set here
+ }
+
+
+ }
+
+ /**
+ * Creates a new handler object to a remote replication server.
+ * @param replicationServerURL The hosting local RS URL.
+ * @param replicationServerId The hosting local RS serverId.
+ * @param replicationServer The hosting local RS object.
+ * @param startECLSessionMsg the start parameters.
+ * @throws DirectoryException when an errors occurs.
+ */
+ public ECLServerHandler(
+ String replicationServerURL,
+ short replicationServerId,
+ ReplicationServer replicationServer,
+ StartECLSessionMsg startECLSessionMsg)
+ throws DirectoryException
+ {
+ // FIXME:ECL queueSize is hard coded to 1 else Handler hangs for some reason
+ super(null, 1, replicationServerURL, replicationServerId,
+ replicationServer, 0);
+ try
+ {
+ setServiceIdAndDomain("cn=changelog");
+ }
+ catch(DirectoryException de)
+ {
+ // no chance to have a bad domain set here
+ }
+ this.initialize(startECLSessionMsg);
+ }
+
+ /**
+ * Starts the handler from a remote start message received from
+ * the remote server.
+ * @param inECLStartMsg The provided ReplServerStart message received.
+ */
+ public void startFromRemoteServer(ServerStartECLMsg inECLStartMsg)
+ {
+ try
+ {
+ // Process start from remote
+ boolean sessionInitiatorSSLEncryption =
+ processStartFromRemote(inECLStartMsg);
+
+ // lock with timeout
+ lockDomain(true);
+
+ // send start to remote
+ ReplServerStartMsg outReplServerStartMsg = sendStartToRemote((short)-1);
+
+ // log
+ logStartHandshakeRCVandSND(inECLStartMsg, outReplServerStartMsg);
+
+ // until here session is encrypted then it depends on the negociation
+ // The session initiator decides whether to use SSL.
+ if (!sessionInitiatorSSLEncryption)
+ session.stopEncryption();
+
+ // wait and process StartSessionMsg from remote RS
+ StartECLSessionMsg inStartECLSessionMsg =
+ waitAndProcessStartSessionECLFromRemoteServer();
+
+ logStartECLSessionHandshake(inStartECLSessionMsg);
+
+ // initialization
+ initialize(inStartECLSessionMsg);
+ }
+ catch(DirectoryException de)
+ {
+ abortStart(de.getMessageObject());
+ }
+ catch(Exception e)
+ {
+ abortStart(Message.raw(e.getLocalizedMessage()));
+ }
+ finally
+ {
+ if ((replicationServerDomain != null) &&
+ replicationServerDomain.hasLock())
+ {
+ replicationServerDomain.release();
+ }
+ }
+ }
+
+ /**
+ * Wait receiving the StartSessionMsg from the remote DS and process it.
+ * @return the startSessionMsg received
+ * @throws DirectoryException
+ * @throws IOException
+ * @throws ClassNotFoundException
+ * @throws DataFormatException
+ * @throws NotSupportedOldVersionPDUException
+ */
+ private StartECLSessionMsg waitAndProcessStartSessionECLFromRemoteServer()
+ throws DirectoryException, IOException, ClassNotFoundException,
+ DataFormatException,
+ NotSupportedOldVersionPDUException
+ {
+ ReplicationMsg msg = null;
+ msg = session.receive();
+
+ if (!(msg instanceof StartECLSessionMsg))
+ {
+ Message message = Message.raw(
+ "Protocol error: StartECLSessionMsg required." + msg + " received.");
+ abortStart(message);
+ }
+
+ // Process StartSessionMsg sent by remote DS
+ StartECLSessionMsg startECLSessionMsg = (StartECLSessionMsg) msg;
+
+ return startECLSessionMsg;
+ }
+
+ /**
+ * Initialize the handler from a provided cookie value.
+ * @param providedGeneralizedStartState The provided cookie value.
+ * @throws DirectoryException When an error is raised.
+ */
+ public void initializeCLSearchFromGenState(
+ String providedGeneralizedStartState)
+ throws DirectoryException
+ {
+ this.cLSearchCtxt.nextSeqnum = -1; // will not generate seqnum
+ initializeCLDomCtxts(providedGeneralizedStartState);
+ }
+
+ /**
+ * Initialize the context for each domain.
+ * @param providedGeneralizedStartState the provided generalized state
+ * @throws DirectoryException When an error occurs.
+ */
+ public void initializeCLDomCtxts(String providedGeneralizedStartState)
+ throws DirectoryException
+ {
+ HashMap<String,ServerState> startStates = new HashMap<String,ServerState>();
+
+ ReplicationServer rs = replicationServerDomain.getReplicationServer();
+
+ try
+ {
+ // Initialize start state for all running domains with empty state
+ Iterator<ReplicationServerDomain> rsdk = rs.getCacheIterator();
+ if (rsdk != null)
+ {
+ while (rsdk.hasNext())
+ {
+ // process a domain
+ ReplicationServerDomain rsd = rsdk.next();
+ // skip the changelog domain
+ if (rsd == this.replicationServerDomain)
+ continue;
+ startStates.put(rsd.getBaseDn(), new ServerState());
+ }
+ }
+
+ // Overwrite start state from the cookie provided in the request
+ if ((providedGeneralizedStartState != null) &&
+ (providedGeneralizedStartState.length()>0))
+ {
+ String[] domains = providedGeneralizedStartState.split(";");
+ for (String domainState : domains)
+ {
+ // Split baseDN and serverState
+ String[] fields = domainState.split(":");
+
+ // BaseDN - Check it
+ String domainBaseDNReceived = fields[0];
+ if (!startStates.containsKey(domainBaseDNReceived))
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
+ "unknown " + domainBaseDNReceived));
+
+ // ServerState
+ ServerState domainServerState = new ServerState();
+ if (fields.length>1)
+ {
+ String strState = fields[1];
+ String[] strCN = strState.split(" ");
+ for (String sr : strCN)
+ {
+ ChangeNumber fromChangeNumber = new ChangeNumber(sr);
+ domainServerState.update(fromChangeNumber);
+ }
+ }
+ startStates.put(domainBaseDNReceived, domainServerState);
+
+ // FIXME: ECL first cookie value check
+ // ECL For each of the provided state, it this state is older
+ // than the older change stored in the replication changelog ....
+ // then a purge occured since the time the cookie was published
+ // it is recommended to do a full resync
+ ReplicationServerDomain rsd =
+ rs.getReplicationServerDomain(domainBaseDNReceived, false);
+ ServerState domainStartState = rsd.getStartState();
+ if (!domainServerState.cover(domainStartState))
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
+ "too old cookie provided " + providedGeneralizedStartState
+ + " first acceptable change for " + rsd.getBaseDn()
+ + " is " + rsd.getStartState()));
+ }
+ }
+ }
+ }
+ catch(DirectoryException de)
+ {
+ throw de;
+ }
+ catch(Exception e)
+ {
+ throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED.get(
+ "Exception raised: " + e.getMessage()));
+ }
+
+ try
+ {
+ // Now traverse all domains and build the initial changelog context
+ Iterator<ReplicationServerDomain> rsdi = rs.getCacheIterator();
+
+ // Creates the table that will contain the real-time info by domain.
+ clDomCtxts = new CLDomainContext[rs.getCacheSize()-1
+ -this.cLSearchCtxt.excludedServiceIDs.size()];
+ int i =0;
+ if (rsdi != null)
+ {
+ while (rsdi.hasNext())
+ {
+ // process a domain
+ ReplicationServerDomain rsd = rsdi.next();
+
+ // skip the 'unreal' changelog domain
+ if (rsd == this.replicationServerDomain)
+ continue;
+
+ // skip the excluded domains
+ boolean excluded = false;
+ for(String excludedServiceID : this.cLSearchCtxt.excludedServiceIDs)
+ {
+ if (excludedServiceID.equalsIgnoreCase(rsd.getBaseDn()))
+ {
+ excluded=true;
+ break;
+ }
+ }
+ if (excluded)
+ continue;
+
+ // Creates the context record
+ CLDomainContext newContext = new CLDomainContext();
+ newContext.active = true;
+ newContext.rsd = rsd;
+
+ if (this.cLSearchCtxt.isPersistent ==
+ StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
+ {
+ newContext.startState = rsd.getCLElligibleState();
+ }
+ else
+ {
+ newContext.startState = startStates.get(rsd.getBaseDn());
+ newContext.stopState = rsd.getCLElligibleState();
+ }
+ newContext.currentState = new ServerState();
+
+ // Creates an unconnected SH
+ MessageHandler mh = new MessageHandler(maxQueueSize,
+ replicationServerURL, replicationServerId, replicationServer);
+ // set initial state
+ mh.setInitialServerState(newContext.startState);
+ // set serviceID and domain
+ mh.setServiceIdAndDomain(rsd.getBaseDn());
+ // register into domain
+ rsd.registerHandler(mh);
+ newContext.mh = mh;
+
+ // store the new context
+ clDomCtxts[i] = newContext;
+ i++;
+ }
+ }
+
+ // the next record from the seqnumdb should be the one
+ cLSearchCtxt.endOfSeqnumdbReached = false;
+ cLSearchCtxt.generalizedStartState = providedGeneralizedStartState;
+
+ // Initializes all domain with the next elligible message
+ for (int j=0; j<clDomCtxts.length; j++)
+ {
+ this.getNextElligibleMessage(j);
+ if (clDomCtxts[j].nextMsg == null)
+ clDomCtxts[j].active = false;
+ }
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ throw new DirectoryException(
+ ResultCode.OPERATIONS_ERROR,
+ Message.raw(Category.SYNC, Severity.INFORMATION,"Exception raised: " +
+ e.getLocalizedMessage()),
+ e);
+ }
+ }
+
+ /**
+ * Registers this handler into its related domain and notifies the domain.
+ */
+ private void registerIntoDomain()
+ {
+ replicationServerDomain.registerHandler(this);
+ }
+
+ /**
+ * Shutdown this handler ServerHandler.
+ */
+ public void shutdown()
+ {
+ for (int i=0;i<clDomCtxts.length;i++)
+ {
+ if (!clDomCtxts[i].rsd.unRegisterHandler(clDomCtxts[i].mh))
+ {
+ TRACER.debugInfo(this +" shutdown() Internal error " +
+ " when unregistering "+ clDomCtxts[i].mh);
+ }
+ clDomCtxts[i].rsd.stopServer(clDomCtxts[i].mh);
+ }
+ super.shutdown();
+ clDomCtxts = null;
+ }
+
+ /**
+ * Request to shutdown the associated writer.
+ */
+ protected void shutdownWriter()
+ {
+ shutdownWriter = true;
+ if (writer!=null)
+ {
+ ECLServerWriter eclWriter = (ECLServerWriter)this.writer;
+ eclWriter.shutdownWriter();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getMonitorInstanceName()
+ {
+ String str = serverURL + " " + String.valueOf(serverId);
+
+ return "Connected External Changelog Server " + str +
+ ",cn=" + replicationServerDomain.getMonitorInstanceName();
+ }
+
+ /**
+ * Retrieves a set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is requested.
+ *
+ * @return A set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is
+ * requested.
+ */
+ @Override
+ public ArrayList<Attribute> getMonitorData()
+ {
+ // Get the generic ones
+ ArrayList<Attribute> attributes = super.getMonitorData();
+
+ // Add the specific RS ones
+ attributes.add(Attributes.create("External-Changelog-Server",
+ serverURL));
+
+ try
+ {
+ MonitorData md;
+ md = replicationServerDomain.computeMonitorData();
+ // FIXME:ECL No monitoring exist for ECL.
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
+ // We failed retrieving the monitor data.
+ attributes.add(Attributes.create("error", message.toString()));
+ }
+ return attributes;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ String localString;
+ localString = "External changelog Server ";
+ if (this.cLSearchCtxt==null)
+ localString += serverId + " " + serverURL + " " + getServiceId();
+ else
+ localString += this.getName();
+ return localString;
+ }
+ /**
+ * Gets the status of the connected DS.
+ * @return The status of the connected DS.
+ */
+ public ServerStatus getStatus()
+ {
+ // FIXME:ECL Sould ECLServerHandler manage a ServerStatus ?
+ return ServerStatus.INVALID_STATUS;
+ }
+ /**
+ * Retrieves the Address URL for this server handler.
+ *
+ * @return The Address URL for this server handler,
+ * in the form of an IP address and port separated by a colon.
+ */
+ public String getServerAddressURL()
+ {
+ return serverAddressURL;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isDataServer()
+ {
+ return true;
+ }
+
+ /**
+ * Initialize the handler.
+ * @param startECLSessionMsg The provided starting state.
+ * @throws DirectoryException when a problem occurs.
+ */
+ public void initialize(StartECLSessionMsg startECLSessionMsg)
+ throws DirectoryException
+ {
+
+ //
+ this.following = false; // FIXME:ECL makes no sense for ECLServerHandler ?
+ this.lateQueue.clear(); // FIXME:ECL makes no sense for ECLServerHandler ?
+ this.setConsumerActive(true);
+ this.cLSearchCtxt.searchPhase = 1;
+ this.operationId = startECLSessionMsg.getOperationId();
+ this.setName(this.getClass().getCanonicalName()+ " " + operationId);
+
+ if (eligibleCNComputerThread==null)
+ eligibleCNComputerThread = new ECLEligibleCNComputerThread();
+
+ cLSearchCtxt.isPersistent = startECLSessionMsg.isPersistent();
+ cLSearchCtxt.stopSeqnum = startECLSessionMsg.getLastDraftChangeNumber();
+ cLSearchCtxt.searchPhase = 1;
+ cLSearchCtxt.currentCookie = new MultiDomainServerState(
+ startECLSessionMsg.getCrossDomainServerState());
+ cLSearchCtxt.excludedServiceIDs=startECLSessionMsg.getExcludedServiceIDs();
+
+ //--
+ if (startECLSessionMsg.getECLRequestType()==0)
+ {
+ initializeCLSearchFromGenState(
+ startECLSessionMsg.getCrossDomainServerState());
+ }
+
+ if (session != null)
+ {
+ try
+ {
+ // Disable timeout for next communications
+ session.setSoTimeout(0);
+ }
+ catch(Exception e) {}
+
+ // sendWindow MUST be created before starting the writer
+ sendWindow = new Semaphore(sendWindowSize);
+
+ // create reader
+ reader = new ServerReader(session, serverId,
+ this, replicationServerDomain);
+ reader.start();
+
+ if (writer == null)
+ {
+ // create writer
+ writer = new ECLServerWriter(session,this,replicationServerDomain);
+ writer.start();
+ }
+
+ // Resume the writer
+ ((ECLServerWriter)writer).resumeWriter();
+
+ // FIXME:ECL Potential race condition if writer not yet resumed here
+ }
+
+ if (cLSearchCtxt.isPersistent == StartECLSessionMsg.PERSISTENT_CHANGES_ONLY)
+ {
+ closePhase1();
+ }
+
+ /* TODO: Good Draft Compat
+ //--
+ if (startCLMsg.getStartMode()==1)
+ {
+ initializeCLSearchFromProvidedSeqnum(startCLMsg.getSequenceNumber());
+ }
+
+ //--
+ if (startCLMsg.getStartMode()==2)
+ {
+ if (CLSearchFromProvidedExactCN(startCLMsg.getChangeNumber()))
+ return;
+ }
+
+ //--
+ if (startCLMsg.getStartMode()==4)
+ {
+ // to get the CL first and last
+ initializeCLDomCtxts(null); // from start
+ ChangeNumber crossDomainElligibleCN = computeCrossDomainElligibleCN();
+
+ try
+ {
+ // to get the CL first and last
+ // last rely on the crossDomainElligibleCN thhus must have been
+ // computed before
+ int[] limits = computeCLLimits(crossDomainElligibleCN);
+ // Send the response
+ CLLimitsMsg msg = new CLLimitsMsg(limits[0], limits[1]);
+ session.publish(msg);
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ try
+ {
+ session.publish(
+ new ErrorMsg(
+ replicationServerDomain.getReplicationServer().getServerId(),
+ serverId,
+ Message.raw(Category.SYNC, Severity.INFORMATION,
+ "Exception raised: " + e.getMessage())));
+ }
+ catch(IOException ioe)
+ {
+ // FIXME: close conn ?
+ }
+ }
+ return;
+ }
+ Good Draft Compat */
+
+ // Store into domain
+ registerIntoDomain();
+
+ }
+
+ /**
+ * Select the next update that must be sent to the server managed by this
+ * ServerHandler.
+ *
+ * @return the next update that must be sent to the server managed by this
+ * ServerHandler.
+ * @exception DirectoryException when an error occurs.
+ */
+ public ECLUpdateMsg takeECLUpdate()
+ throws DirectoryException
+ {
+ boolean interrupted = true;
+ ECLUpdateMsg msg = getnextUpdate();
+
+ // FIXME:ECL We should refactor so that a SH always have a session
+ if (session == null)
+ return msg;
+
+ /*
+ * When we remove a message from the queue we need to check if another
+ * server is waiting in flow control because this queue was too long.
+ * This check might cause a performance penalty an therefore it
+ * is not done for every message removed but only every few messages.
+ */
+ /** FIXME:ECL checkAllSaturation makes no sense for ECLServerHandler ?
+ if (++saturationCount > 10)
+ {
+ saturationCount = 0;
+ try
+ {
+ replicationServerDomain.checkAllSaturation();
+ } catch (IOException e)
+ {
+ }
+ }
+ */
+ boolean acquired = false;
+ do
+ {
+ try
+ {
+ acquired = sendWindow.tryAcquire((long) 500, TimeUnit.MILLISECONDS);
+ interrupted = false;
+ } catch (InterruptedException e)
+ {
+ // loop until not interrupted
+ }
+ } while (((interrupted) || (!acquired)) && (!shutdownWriter));
+ if (msg != null)
+ {
+ incrementOutCount();
+ }
+ return msg;
+ }
+
+ /**
+ * Get the next message - non blocking - null when none.
+ *
+ * @param synchronous - not used - always non blocking.
+ * @return the next message - null when none.
+ */
+ protected UpdateMsg getnextMessage(boolean synchronous)
+ {
+ UpdateMsg msg = null;
+ try
+ {
+ ECLUpdateMsg eclMsg = getnextUpdate();
+ if (eclMsg!=null)
+ msg = eclMsg.getUpdateMsg();
+ }
+ catch(DirectoryException de)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+ return msg;
+ }
+
+ /**
+ * Get the next external changelog update.
+ *
+ * @return The ECL update, null when none.
+ * @exception DirectoryException when any problem occurs.
+ */
+ protected ECLUpdateMsg getnextUpdate()
+ throws DirectoryException
+ {
+ return getGeneralizedNextECLUpdate(this.cLSearchCtxt);
+ }
+
+ /**
+ * Computes the cross domain eligible message (non blocking).
+ * Return null when search is covered
+ */
+ private ECLUpdateMsg getGeneralizedNextECLUpdate(CLTraverseCtxt cLSearchCtxt)
+ throws DirectoryException
+ {
+ ECLUpdateMsg theOldestChange = null;
+ try
+ {
+ TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + "," + this +
+ " getGeneralizedNextECLUpdate starts with ctxt="
+ + cLSearchCtxt);
+
+ if ((cLSearchCtxt.nextSeqnum != -1) &&
+ (!cLSearchCtxt.endOfSeqnumdbReached))
+ {
+ /* TODO:ECL G Good changelog draft compat.
+ // First time , initialise the cursor to traverse the seqnumdb
+ if (seqnumDbReadIterator == null)
+ {
+ try
+ {
+ seqnumDbReadIterator = replicationServerDomain.getReplicationServer().
+ getSeqnumDbHandler().generateIterator(cLSearchCtxt.nextSeqnum);
+ TRACER.debugInfo("getGeneralizedNextMessage(): "
+ + " creates seqnumDbReadIterator from nextSeqnum="
+ + cLSearchCtxt.nextSeqnum
+ + " 1rst=" + seqnumDbReadIterator.getSeqnum()
+ + " CN=" + seqnumDbReadIterator.getChangeNumber()
+ + cLSearchCtxt);
+ }
+ catch(Exception e)
+ {
+ cLSearchCtxt.endOfSeqnumdbReached = true;
+ }
+ }
+ */
+ }
+
+
+ // Search / no seqnum / not persistent
+ // -----------------------------------
+ // init: all domain are candidate
+ // get one msg from each
+ // no (null) msg returned: should not happen since we go to a state
+ // that is computed/expected
+ // getMessage:
+ // get the oldest msg:
+ // after:
+ // if stopState of domain is covered then domain is out candidate
+ // until no domain candidate mean generalized stopState
+ // has been reached
+ // else
+ // get one msg from that domain
+ // no (null) msg returned: should not happen since we go to a state
+ // that is computed/expected
+ // step 2: send DoneMsg
+
+ // Persistent:
+ // ----------
+ // step 1&2: same as non persistent
+ //
+ // step 3: reinit all domain are candidate
+ // take the oldest
+ // if one domain has no msg, still is candidate
+
+ int iDom = 0;
+ boolean nextclchange = true;
+ while ((nextclchange) && (cLSearchCtxt.searchPhase==1))
+ {
+
+ // Step 1 & 2
+ if (cLSearchCtxt.searchPhase==1)
+ {
+ if (debugEnabled())
+ clDomCtxtsToString("In getGeneralizedNextMessage : " +
+ "looking for the generalized oldest change");
+
+ // Retrieves the index in the subx table of the
+ // generalizedOldestChange
+ iDom = getGeneralizedOldestChange();
+
+ // idomain != -1 means that we got one
+ // generalized oldest change to process
+ if (iDom==-1)
+ {
+ closePhase1();
+
+ // signify end of phase 1 to the caller
+ return null;
+ }
+
+ // idomain != -1 means that we got one
+ // generalized oldest change to process
+ String suffix = this.clDomCtxts[iDom].rsd.getBaseDn();
+ theOldestChange = new ECLUpdateMsg(
+ (LDAPUpdateMsg)clDomCtxts[iDom].nextMsg,
+ null, // set later
+ suffix);
+
+ nextclchange = false;
+
+ /* TODO:ECL G Good change log draft compat.
+ if (cLSearchCtxt.nextSeqnum!=-1)
+ {
+ // Should either retrieve or generate a seqnum
+ // we also need to check if the seqnumdb is acccurate reagrding
+ // the changelogdb.
+ // if not, 2 potential reasons
+ // -1 : purge from the changelog .. let's traverse the seqnumdb
+ // -2 : changelog is late .. let's traverse the changelog
+
+ // replogcn : the oldest change from the changelog db
+ ChangeNumber replogcn = theOldestChange.getChangeNumber();
+ DN replogReplDomDN = clDomCtxts[iDom].rsd.getBaseDn();
+
+ while (true)
+ {
+ if (!cLSearchCtxt.endOfSeqnumdbReached)
+ {
+ // we did not reach yet the end of the seqnumdb
+
+ // seqnumcn : the next change from from the seqnum db
+ ChangeNumber seqnumcn = seqnumDbReadIterator.getChangeNumber();
+
+ // are replogcn and seqnumcn should be the same change ?
+ int cmp = replogcn.compareTo(seqnumcn);
+ DN seqnumReplDomDN=DN.decode(seqnumDbReadIterator.
+ getDomainDN());
+
+ TRACER.debugInfo("seqnumgen: comparing the 2 db "
+ + " changelogdb:" + replogReplDomDN + "=" + replogcn
+ + " ts=" +
+ new Date(replogcn.getTime()).toString()
+ + "## seqnumdb:" + seqnumReplDomDN + "=" + seqnumcn
+ + " ts=" +
+ new Date(seqnumcn.getTime()).toString()
+ + " sn older=" + seqnumcn.older(replogcn));
+
+ if ((replogReplDomDN.compareTo(seqnumReplDomDN)==0) && (cmp==0))
+ {
+ // same domain and same CN => same change
+
+ // assign the seqnum from the seqnumdb
+ // to the change from the changelogdb
+
+ TRACER.debugInfo("seqnumgen: assigning seqnum="
+ + seqnumDbReadIterator.getSeqnum()
+ + " to change=" + theOldestChange);
+ theOldestChange.setSeqnum(seqnumDbReadIterator.getSeqnum());
+
+ // prepare the next seqnum for the potential next change added
+ // to the seqnumDb
+ cLSearchCtxt.nextSeqnum = seqnumDbReadIterator.getSeqnum()
+ + 1;
+ break;
+ }
+ else
+ {
+ // replogcn and seqnumcn are NOT the same change
+ if (seqnumcn.older(replogcn))
+ {
+ // the change from the seqnumDb is older
+ // that means that the change has been purged from the
+ // changelog
+ try
+ {
+ // let's traverse the seqnumdb searching for the change
+ // found in the changelogDb.
+ TRACER.debugInfo("seqnumgen: will skip "
+ + seqnumcn + " and next from the seqnum");
+ cLSearchCtxt.endOfSeqnumdbReached =
+ (seqnumDbReadIterator.next()==false);
+ TRACER.debugInfo("seqnumgen: has nexted cr to "
+ + " sn=" + seqnumDbReadIterator.getSeqnum()
+ + " cn=" + seqnumDbReadIterator.getChangeNumber()
+ + " and reached end "
+ + " of seqnumdb:"+cLSearchCtxt.endOfSeqnumdbReached);
+ if (cLSearchCtxt.endOfSeqnumdbReached)
+ {
+ // we are at the end of the seqnumdb in the append mode
+ // store in seqnumdb the pair
+ // seqnum of the cur change,state before this change)
+ replicationServerDomain.addSeqnum(
+ cLSearchCtxt.nextSeqnum,
+ getGenState(),
+ clDomCtxts[iDom].rsd.getBaseDn().toNormalizedString(),
+ theOldestChange.getChangeNumber());
+ theOldestChange.setSeqnum(cLSearchCtxt.nextSeqnum);
+ cLSearchCtxt.nextSeqnum++;
+ break;
+ }
+ else
+ {
+ // next change from seqnumdb
+ cLSearchCtxt.nextSeqnum =
+ seqnumDbReadIterator.getSeqnum() + 1;
+ continue;
+ }
+ }
+ catch(Exception e)
+ {
+ }
+ }
+ else
+ {
+ // the change from the changelogDb is older
+ // it should have been stored lately
+ // let's continue to traverse the changelogdb
+ TRACER.debugInfo("seqnumgen: will skip "
+ + replogcn + " and next from the CL");
+ nextclchange = true;
+ break; // TO BE CHECKED
+ }
+ }
+ }
+ else
+ {
+ // we are at the end of the seqnumdb in the append mode
+ // store in seqnumdb the pair
+ // (seqnum of the current change, state before this change)
+ replicationServerDomain.addSeqnum(
+ cLSearchCtxt.nextSeqnum,
+ getGenState(),
+ clDomCtxts[iDom].rsd.getBaseDn().toNormalizedString(),
+ theOldestChange.getChangeNumber());
+ theOldestChange.setSeqnum(cLSearchCtxt.nextSeqnum);
+ cLSearchCtxt.nextSeqnum++;
+ break;
+ }
+ } // while seqnum
+ } // nextseqnum !- -1
+ */
+
+ // here we have the right oldest change and in the seqnum case we
+ // have its seqnum
+
+ // Set and test the domain of the oldestChange see if we reached
+ // the end of the phase for this domain
+ clDomCtxts[iDom].currentState.update(
+ theOldestChange.getUpdateMsg().getChangeNumber());
+
+ if (clDomCtxts[iDom].currentState.cover(clDomCtxts[iDom].stopState))
+ {
+ clDomCtxts[iDom].active = false;
+ }
+
+ // Test the seqnum of the oldestChange see if we reached
+ // the end of operation
+ /* TODO:ECL G Good changelog draft compat. Not yet implemented
+ if ((cLSearchCtxt.stopSeqnum>0) &&
+ (theOldestChange.getSeqnum()>=cLSearchCtxt.stopSeqnum))
+ {
+ closePhase1();
+
+ // means end of phase 1 to the calling writer
+ return null;
+ }
+ */
+
+ if (clDomCtxts[iDom].active)
+ {
+ // populates the table with the next eligible msg from idomain
+ // in non blocking mode, return null when no more eligible msg
+ getNextElligibleMessage(iDom);
+ }
+ } // phase ==1
+ } // while (nextclchange)
+
+ if (cLSearchCtxt.searchPhase==2)
+ {
+ clDomCtxtsToString("In getGeneralizedNextMessage (persistent): " +
+ "looking for the generalized oldest change");
+
+ for (int ido=0; ido<clDomCtxts.length; ido++)
+ {
+ // get next msg
+ getNextElligibleMessage(ido);
+ }
+
+ // take the oldest one
+ iDom = getGeneralizedOldestChange();
+
+ if (iDom != -1)
+ {
+ String suffix = this.clDomCtxts[iDom].rsd.getBaseDn();
+
+ theOldestChange = new ECLUpdateMsg(
+ (LDAPUpdateMsg)clDomCtxts[iDom].nextMsg,
+ null, // set later
+ suffix);
+
+ clDomCtxts[iDom].currentState.update(
+ theOldestChange.getUpdateMsg().getChangeNumber());
+
+ /* TODO:ECL G Good changelog draft compat.
+ if (cLSearchCtxt.nextSeqnum!=-1)
+ {
+ // should generate seqnum
+
+ // store in seqnumdb the pair
+ // (seqnum of the current change, state before this change)
+ replicationServerDomain.addSeqnum(
+ cLSearchCtxt.nextSeqnum,
+ getGenState(),
+ clDomCtxts[iDom].rsd.getBaseDn().toNormalizedString(),
+ theOldestChange.getChangeNumber());
+ theOldestChange.setSeqnum(cLSearchCtxt.nextSeqnum);
+ cLSearchCtxt.nextSeqnum++;
+ }
+ */
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ throw new DirectoryException(
+ ResultCode.OPERATIONS_ERROR,
+ Message.raw(Category.SYNC, Severity.INFORMATION,"Exception raised: "),
+ e);
+ }
+
+ if (theOldestChange != null)
+ {
+ // Update the current state
+ this.cLSearchCtxt.currentCookie.update(
+ theOldestChange.getServiceId(),
+ theOldestChange.getUpdateMsg().getChangeNumber());
+
+ // Set the current value of global state in the returned message
+ theOldestChange.setCookie(this.cLSearchCtxt.currentCookie);
+ }
+ return theOldestChange;
+ }
+
+ /**
+ * Terminates the first (non persistent) phase of the search on the ECL.
+ */
+ private void closePhase1()
+ {
+ // starvation of changelog messages
+ // all domain have been unactived means are covered
+ if (debugEnabled())
+ TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + "," + this + " closePhase1()"
+ + " searchCtxt=" + cLSearchCtxt);
+
+ // go to persistent phase if one
+ for (int i=0; i<clDomCtxts.length; i++)
+ clDomCtxts[i].active = true;
+
+ if (this.cLSearchCtxt.isPersistent != StartECLSessionMsg.NON_PERSISTENT)
+ {
+ // phase = 1 done AND persistent search => goto phase 2
+ cLSearchCtxt.searchPhase=2;
+
+ if (writer ==null)
+ {
+ writer = new ECLServerWriter(session,this,replicationServerDomain);
+ writer.start(); // start suspended
+ }
+
+ }
+ else
+ {
+ // phase = 1 done AND !persistent search => reinit to phase 0
+ cLSearchCtxt.searchPhase=0;
+ }
+
+ /* TODO:ECL G Good changelog draft compat.
+ if (seqnumDbReadIterator!=null)
+ {
+ // End of phase 1 => always release the seqnum iterator
+ seqnumDbReadIterator.releaseCursor();
+ seqnumDbReadIterator = null;
+ }
+ */
+ }
+
+ /**
+ * Get the oldest change contained in the subx table.
+ * The subx table should be populated before
+ * @return the oldest change.
+ */
+ private int getGeneralizedOldestChange()
+ {
+ int oldest = -1;
+ for (int i=0; i<clDomCtxts.length; i++)
+ {
+ if ((clDomCtxts[i].active))
+ {
+ // on the first loop, oldest==-1
+ // .msg is null when the previous (non blocking) nextMessage did
+ // not have any eligible msg to return
+ if (clDomCtxts[i].nextMsg != null)
+ {
+ if ((oldest==-1) ||
+ (clDomCtxts[i].nextMsg.compareTo(clDomCtxts[oldest].nextMsg)<0))
+ {
+ oldest = i;
+ }
+ }
+ }
+ }
+ TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName()
+ + "," + this + " getGeneralizedOldestChange() " +
+ " returns " +
+ ((oldest!=-1)?clDomCtxts[oldest].nextMsg:""));
+
+ return oldest;
+ }
+
+ /**
+ * Get from the provided domain, the next message elligible regarding
+ * the crossDomain elligible CN. Put it in the context table.
+ * @param idomain the provided domain.
+ */
+ private void getNextElligibleMessage(int idomain)
+ {
+ ChangeNumber crossDomainElligibleCN = computeCrossDomainElligibleCN();
+ try
+ {
+ if (clDomCtxts[idomain].nonElligiblemsg != null)
+ {
+ TRACER.debugInfo("getNextElligibleMessage tests if the already " +
+ " stored nonElligibleMsg has becoem elligible regarding " +
+ " the crossDomainElligibleCN ("+crossDomainElligibleCN +
+ " ) " +
+ clDomCtxts[idomain].nonElligiblemsg.getChangeNumber().
+ older(crossDomainElligibleCN));
+ // we already got the oldest msg and it was not elligible
+ if (clDomCtxts[idomain].nonElligiblemsg.getChangeNumber().
+ older(crossDomainElligibleCN))
+ {
+ // it is now elligible
+ clDomCtxts[idomain].nextMsg = clDomCtxts[idomain].nonElligiblemsg;
+ clDomCtxts[idomain].nonElligiblemsg = null;
+ }
+ else
+ {
+ // the oldest is still not elligible - let's wait next
+ }
+ }
+ else
+ {
+ // non blocking
+ UpdateMsg newMsg = clDomCtxts[idomain].mh.getnextMessage(false);
+ TRACER.debugInfo(this +
+ " getNextElligibleMessage got the next changelogmsg "
+ + " from " + clDomCtxts[idomain].mh.getServiceId()
+ + " newCLMsg=" + newMsg);
+ clDomCtxts[idomain].nextMsg =
+ clDomCtxts[idomain].nonElligiblemsg = null;
+ // in non blocking mode, return null when no more msg
+ if (newMsg != null)
+ {
+ /* TODO:ECL Take into account eligibility.
+ TRACER.debugInfo("getNextElligibleMessage is "
+ + newMsg.getChangeNumber()
+ + new Date(newMsg.getChangeNumber().getTime()).toString()
+ + " elligible "
+ + newMsg.getChangeNumber().older(crossDomainElligibleCN));
+ if (newMsg.getChangeNumber().older(crossDomainElligibleCN))
+ {
+ // is elligible
+ clDomCtxts[idomain].nextMsg = newMsg;
+ }
+ else
+ {
+ // is not elligible
+ clDomCtxts[idomain].nonElligiblemsg = newMsg;
+ }
+ */
+ clDomCtxts[idomain].nextMsg = newMsg;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+
+ /*
+ */
+ ECLEligibleCNComputerThread eligibleCNComputerThread = null;
+ ChangeNumber liveecn;
+ private ChangeNumber computeCrossDomainElligibleCN()
+ {
+ return liveecn;
+ }
+
+
+ /**
+ * This class specifies the thread that computes periodically
+ * the cross domain eligible CN.
+ */
+ private final class ECLEligibleCNComputerThread
+ extends DirectoryThread
+ {
+ /**
+ * The tracer object for the debug logger.
+ */
+ private boolean shutdown = false;
+
+ private ECLEligibleCNComputerThread()
+ {
+ super("ECL eligible CN computer thread");
+ start();
+ }
+
+ public void run()
+ {
+ while (shutdown == false)
+ {
+ try {
+ synchronized (this)
+ {
+ liveecn = computeNewCrossDomainElligibleCN();
+ try
+ {
+ this.wait(1000);
+ } catch (InterruptedException e)
+ { }
+ }
+ } catch (Exception end)
+ {
+ break;
+ }
+ }
+ }
+
+ private ChangeNumber computeNewCrossDomainElligibleCN()
+ {
+ ChangeNumber computedCrossDomainElligibleCN = null;
+ String s = "=> ";
+
+ ReplicationServer rs = replicationServerDomain.getReplicationServer();
+ TRACER.debugInfo("ECLSH.computeNewCrossDomainElligibleCN() "
+ + " periodic starts rs="+rs);
+
+ Iterator<ReplicationServerDomain> rsdi = rs.getCacheIterator();
+ if (rsdi != null)
+ {
+ while (rsdi.hasNext())
+ {
+ ReplicationServerDomain domain = rsdi.next();
+ if (domain.getBaseDn().equalsIgnoreCase("cn=changelog"))
+ continue;
+
+ ChangeNumber domainElligibleCN = computeEligibleCN(domain);
+ if (domainElligibleCN==null)
+ continue;
+ if ((computedCrossDomainElligibleCN == null) ||
+ (domainElligibleCN.older(computedCrossDomainElligibleCN)))
+ {
+ computedCrossDomainElligibleCN = domainElligibleCN;
+ }
+ s += "\n DN:" + domain.getBaseDn()
+ + "\t\t domainElligibleCN :" + domainElligibleCN
+ + "/" +
+ new Date(domainElligibleCN.getTime()).toString();
+ }
+ }
+
+ TRACER.debugInfo("SH.computeNewCrossDomainElligibleCN() periodic " +
+ " ends with " +
+ " the following domainElligibleCN for each domain :" + s +
+ "\n thus CrossDomainElligibleCN=" + computedCrossDomainElligibleCN +
+ " ts=" +
+ new Date(computedCrossDomainElligibleCN.getTime()).toString());
+
+ return computedCrossDomainElligibleCN;
+ }
+ }
+
+ /**
+ * Compute the eligible CN.
+ * @param rsd The provided replication server domain for which we want
+ * to retrieve the eligible date.
+ * @return null if the domain does not play in eligibility.
+ */
+ public ChangeNumber computeEligibleCN(ReplicationServerDomain rsd)
+ {
+ ChangeNumber elligibleCN = null;
+ ServerState heartbeatState = rsd.getHeartbeatState();
+ if (heartbeatState==null)
+ return null;
+
+ // compute elligible CN
+ ServerState hbState = heartbeatState.duplicate();
+
+ Iterator<Short> it = hbState.iterator();
+ while (it.hasNext())
+ {
+ short sid = it.next();
+ ChangeNumber storedCN = hbState.getMaxChangeNumber(sid);
+
+ // If the most recent UpdateMsg or CLHeartbeatMsg received is very old
+ // then the server is considered down and not considered for eligibility
+ if (TimeThread.getTime()-storedCN.getTime()>2000)
+ {
+ TRACER.debugInfo(
+ "For RSD." + rsd.getBaseDn() + " Server " + sid
+ + " is not considered for eligibility ... potentially down");
+ continue;
+ }
+
+ if ((elligibleCN == null) || (storedCN.older(elligibleCN)))
+ {
+ elligibleCN = storedCN;
+ }
+ }
+ TRACER.debugInfo(
+ "For RSD." + rsd.getBaseDn() + " ElligibleCN()=" + elligibleCN);
+ return elligibleCN;
+ }
+
+ /**
+ * Returns the client operation id.
+ * @return The client operation id.
+ */
+ public String getOperationId()
+ {
+ return operationId;
+ }
+
+ /**
+ * Getter for the persistent property of the current search.
+ * @return Whether the current search is persistent or not.
+ */
+ public short isPersistent() {
+ return this.cLSearchCtxt.isPersistent;
+ }
+
+ /**
+ * Getter for the current search phase (INIT or PERSISTENT).
+ * @return Whether the current search is persistent or not.
+ */
+ public int getSearchPhase() {
+ return this.cLSearchCtxt.searchPhase;
+ }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
new file mode 100644
index 0000000..642fa9c
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ECLServerWriter.java
@@ -0,0 +1,306 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.io.IOException;
+import java.net.SocketException;
+
+import org.opends.messages.Message;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.protocol.DoneMsg;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.workflowelement.externalchangelog.ECLSearchOperation;
+import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
+
+
+/**
+ * This class defines a server writer, which is used to send changes to a
+ * directory server.
+ */
+public class ECLServerWriter extends ServerWriter
+{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = getTracer();
+
+ private ProtocolSession session;
+ private ECLServerHandler handler;
+ private ReplicationServerDomain replicationServerDomain;
+ private short protocolVersion = -1;
+ private boolean suspended;
+ private boolean shutdown;
+
+ /**
+ * Create a ServerWriter.
+ *
+ * @param session the ProtocolSession that will be used to send updates.
+ * @param handler ECL handler for which the ServerWriter is created.
+ * @param replicationServerDomain the ReplicationServerDomain of this
+ * ServerWriter.
+ */
+ public ECLServerWriter(ProtocolSession session, ECLServerHandler handler,
+ ReplicationServerDomain replicationServerDomain)
+ {
+ super(session, (short)-1, handler, replicationServerDomain);
+
+ setName("Replication ECL Writer Thread for op:" +
+ handler.getOperationId());
+
+ this.session = session;
+ this.handler = handler;
+ this.replicationServerDomain = replicationServerDomain;
+ // Keep protocol version locally for efficiency
+ this.protocolVersion = handler.getProtocolVersion();
+ this.suspended = false;
+ this.shutdown = false;
+ }
+
+ /**
+ * The writer will start suspended by the Handler for the CL
+ * waiting for the startCLSessionMsg. Then it may be
+ * suspended between 2 jobs, each job being a separate search.
+ */
+ public synchronized void suspendWriter()
+ {
+ synchronized(this)
+ {
+ suspended = true;
+ }
+ }
+
+ /**
+ * Resume the writer.
+ */
+ public synchronized void resumeWriter()
+ {
+ synchronized(this)
+ {
+ suspended = false;
+ }
+ notify();
+ }
+
+ /**
+ * Run method for the ServerWriter.
+ * Loops waiting for changes from the ReplicationServerDomain and
+ * forward them to the other servers
+ */
+ public void run()
+ {
+ Message errMessage = null;
+ try
+ {
+ while (true)
+ {
+ // wait to be resumed or shutdowned
+ if ((suspended) && (!shutdown))
+ {
+ synchronized(this)
+ {
+ wait();
+ }
+ }
+
+ if (shutdown)
+ {
+ return;
+ }
+
+ // Not suspended
+ doIt();
+
+
+ if (shutdown)
+ {
+ return;
+ }
+ suspendWriter();
+ }
+ }
+ catch (SocketException e)
+ {
+ // Just ignore the exception and let the thread die as well
+ errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+ "for operation " + handler.getOperationId());
+ logError(errMessage);
+ }
+ catch (Exception e)
+ {
+ // An unexpected error happened.
+ // Log an error and close the connection.
+ errMessage = ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
+ " " + stackTraceToSingleLineString(e));
+ logError(errMessage);
+ }
+ finally
+ {
+ if (session!=null)
+ {
+ try
+ {
+ session.close();
+ } catch (IOException e)
+ {
+ // Can't do much more : ignore
+ }
+ }
+ replicationServerDomain.stopServer(handler);
+ }
+ }
+
+ /**
+ * Loop gettting changes from the domain and publishing them either to
+ * the provided session or to the ECL session interface.
+ * @throws IOException when raised (connection closure)
+ * @throws InterruptedException when raised
+ */
+ public void doIt()
+ throws IOException, InterruptedException
+ {
+ ECLUpdateMsg update = null;
+ while (true)
+ {
+ if (shutdown)
+ {
+ return;
+ }
+
+ if (suspended)
+ {
+ return;
+ }
+
+ // TODO:ECL please document the details of the cases where update could
+ // be not null here
+ if (update == null)
+ {
+ try
+ {
+ update = handler.takeECLUpdate();
+ }
+ catch(DirectoryException de)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+ }
+
+ if (update == null)
+ {
+ if (handler.getSearchPhase() != 1)
+ {
+ if (session!=null)
+ {
+ // session is null in pusherOnly mode
+ // Done is used to end phase 1
+ session.publish(new DoneMsg(
+ replicationServerDomain.getReplicationServer().getServerId(),
+ handler.getServerId()), protocolVersion);
+ }
+ }
+
+ if (handler.isPersistent() == StartECLSessionMsg.NON_PERSISTENT)
+ {
+ // publishing is normally stopped here
+ break;
+ }
+ else
+ {
+ // except if we are in persistent search
+ Thread.sleep(200);
+ continue;
+ }
+ }
+ else
+ {
+ // Publish the update to the remote server using a protocol version he
+ // it supports
+ publish(update, protocolVersion);
+ update = null;
+ }
+ }
+}
+
+ /**
+ * Shutdown the writer.
+ */
+ public synchronized void shutdownWriter()
+ {
+ shutdown = true;
+ this.notify();
+ }
+
+ /**
+ * Publish a change either on the protocol session or to a persistent search.
+ */
+ private void publish(ECLUpdateMsg msg, short reqProtocolVersion)
+ throws IOException
+ {
+ if (debugEnabled())
+ TRACER.debugInfo(this + " publishes msg=[" + msg.toString() + "]");
+
+ if (session!=null)
+ {
+ session.publish(msg, protocolVersion);
+ }
+ else
+ {
+ ECLWorkflowElement wfe = (ECLWorkflowElement)
+ DirectoryServer.getWorkflowElement(
+ ECLWorkflowElement.ECL_WORKFLOW_ELEMENT);
+
+ // Notify persistent searches.
+ for (PersistentSearch psearch : wfe.getPersistentSearches())
+ {
+ try
+ {
+ Entry eclEntry = ECLSearchOperation.createEntryFromMsg(msg);
+ psearch.processAdd(eclEntry, -1);
+ }
+ catch(Exception e)
+ {
+ Message errMessage =
+ ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
+ " " + stackTraceToSingleLineString(e));
+ logError(errMessage);
+ }
+ }
+ }
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java
new file mode 100644
index 0000000..2bf4bf1
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ExternalChangeLogSessionImpl.java
@@ -0,0 +1,85 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import org.opends.server.replication.common.ExternalChangeLogSession;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.types.DirectoryException;
+
+/**
+ * This interface defines a session used to search the external changelog
+ * in the Directory Server.
+ */
+public class ExternalChangeLogSessionImpl
+ implements ExternalChangeLogSession
+{
+
+ ECLServerHandler handler;
+ ReplicationServer rs;
+
+ /**
+ * Create a new external changelog session.
+ * @param rs The replication server to which we will request the log.
+ * @param startECLSessionMsg The start session message containing the
+ * details of the search request on the ECL.
+ * @throws DirectoryException When an error occurs.
+ */
+ public ExternalChangeLogSessionImpl(
+ ReplicationServer rs,
+ StartECLSessionMsg startECLSessionMsg)
+ throws DirectoryException
+ {
+ this.rs = rs;
+
+ this.handler = new ECLServerHandler(
+ rs.getServerURL(),
+ rs.getServerId(),
+ rs,
+ startECLSessionMsg);
+ }
+
+ /**
+ * Returns the next message available for the ECL (blocking)
+ * null when none.
+ * @return the next available message from the ECL.
+ * @throws DirectoryException when needed.
+ */
+ public ECLUpdateMsg getNextUpdate()
+ throws DirectoryException
+ {
+ return handler.getnextUpdate();
+ }
+
+ /**
+ * Close the session.
+ */
+ public void close()
+ {
+ handler.getDomain().stopServer(handler);
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
index b4634fc..eae7115 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/LightweightServerHandler.java
@@ -64,7 +64,7 @@
// The tracer object for the debug logger.
private static final DebugTracer TRACER = getTracer();
- private ServerHandler replServerHandler;
+ private ReplicationServerHandler replServerHandler;
private ReplicationServerDomain rsDomain;
// The id of the RS this DS is connected to
private short replicationServerId = -1;
@@ -103,7 +103,7 @@
* @param assuredMode The assured mode of the remote DS
* @param safeDataLevel The safe data level of the remote DS
*/
- public LightweightServerHandler(ServerHandler replServerHandler,
+ public LightweightServerHandler(ReplicationServerHandler replServerHandler,
short replicationServerId, short serverId, long generationId, byte groupId,
ServerStatus status, List<String> refUrls, boolean assuredFlag,
AssuredMode assuredMode, byte safeDataLevel)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java
new file mode 100644
index 0000000..cadb898
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/MessageHandler.java
@@ -0,0 +1,912 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import static org.opends.messages.ReplicationMessages.ERR_RS_DN_DOES_NOT_MATCH;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
+import java.util.ArrayList;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.MonitorProviderCfg;
+import org.opends.server.api.MonitorProvider;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
+
+/**
+ * This class implements a buffering/producer/consumer mechanism of
+ * replication changes (UpdateMsg) used inside the replication server.
+ *
+ * MessageHandlers are registered into Replication server domains.
+ * When an update message is received by a domain, the domain forwards
+ * the message to the registered message handlers.
+ * Message are buffered into a queue.
+ * Consumers are expected to come and consume the UpdateMsg from the queue.
+ */
+public class MessageHandler extends MonitorProvider<MonitorProviderCfg>
+{
+
+ /**
+ * The tracer object for the debug logger.
+ */
+ protected static final DebugTracer TRACER = getTracer();
+ /**
+ * UpdateMsg queue.
+ */
+ private final MsgQueue msgQueue = new MsgQueue();
+ /**
+ * Late queue.
+ */
+ protected MsgQueue lateQueue = new MsgQueue();
+ /**
+ * Local hosting RS.
+ */
+ protected ReplicationServer replicationServer = null;
+ /**
+ * The URL of the hosting replication server.
+ */
+ protected String replicationServerURL = null;
+ /**
+ * The serverID of the hosting replication server.
+ */
+ protected short replicationServerId;
+ /**
+ * Specifies the related replication server domain based on serviceId(baseDn).
+ */
+ protected ReplicationServerDomain replicationServerDomain = null;
+ /**
+ * Number of update sent to the server.
+ */
+ protected int outCount = 0;
+ /**
+ * Number of updates received from the server.
+ */
+ protected int inCount = 0;
+ /**
+ * Specifies the max receive queue for this handler.
+ */
+ protected int maxReceiveQueue = 0;
+ /**
+ * Specifies the max send queue for this handler.
+ */
+ protected int maxSendQueue = 0;
+ /**
+ * Specifies the max receive delay for this handler.
+ */
+ protected int maxReceiveDelay = 0;
+ /**
+ * Specifies the max send delay for this handler.
+ */
+ protected int maxSendDelay = 0;
+ /**
+ * Specifies the max queue size for this handler.
+ */
+ protected int maxQueueSize = 5000;
+ /**
+ * Specifies the max queue size in bytes for this handler.
+ */
+ protected int maxQueueBytesSize = maxQueueSize * 100;
+ /**
+ * Specifies the max restart receive queue for this handler.
+ */
+ protected int restartReceiveQueue;
+ /**
+ * Specifies the max restart send queue for this handler.
+ */
+ protected int restartSendQueue;
+ /**
+ * Specifies the max restart receive delay for this handler.
+ */
+ protected int restartReceiveDelay;
+ /**
+ * Specifies the max restart send delay for this handler.
+ */
+ protected int restartSendDelay;
+ /**
+ * Specifies whether the consumer is following the producer (is not late).
+ */
+ protected boolean following = false;
+ /**
+ * Specifies the current serverState of this handler.
+ */
+ private ServerState serverState;
+ /**
+ * Specifies the identifier of the service (usually the baseDn of the domain).
+ */
+ private String serviceId = null;
+ /**
+ * Specifies whether the server is flow controlled and should be stopped from
+ * sending messages.
+ */
+ protected boolean flowControl = false;
+ /**
+ * Specifies whether the consumer is still active or not.
+ * If not active, the handler will not return any message.
+ * Called at the beginning of shutdown process.
+ */
+ private boolean activeConsumer = true;
+ /**
+ * Set when ServerHandler is stopping.
+ */
+ private AtomicBoolean shuttingDown = new AtomicBoolean(false);
+
+ /**
+ * Creates a new server handler instance with the provided socket.
+ * @param queueSize The maximum number of update that will be kept
+ * in memory by this ServerHandler.
+ * @param replicationServerURL The URL of the hosting replication server.
+ * @param replicationServerId The ID of the hosting replication server.
+ * @param replicationServer The hosting replication server.
+ */
+ public MessageHandler(
+ int queueSize,
+ String replicationServerURL,
+ short replicationServerId,
+ ReplicationServer replicationServer)
+ {
+ super("Message Handler");
+ this.maxQueueSize = queueSize;
+ this.maxQueueBytesSize = queueSize * 100;
+ this.replicationServerURL = replicationServerURL;
+ this.replicationServerId = replicationServerId;
+ this.replicationServer = replicationServer;
+ }
+
+ /**
+ * Add an update to the list of updates that must be sent to the server
+ * managed by this Handler.
+ *
+ * @param update The update that must be added to the list of updates of
+ * this handler.
+ * @param sourceHandler The source handler that generated the update.
+ */
+ public void add(UpdateMsg update, MessageHandler sourceHandler)
+ {
+ synchronized (msgQueue)
+ {
+ /*
+ * If queue was empty the writer thread was probably asleep
+ * waiting for some changes, wake it up
+ */
+ if (msgQueue.isEmpty())
+ msgQueue.notify();
+
+ msgQueue.add(update);
+
+ /* TODO : size should be configurable
+ * and larger than max-receive-queue-size
+ */
+ while ((msgQueue.count() > maxQueueSize) ||
+ (msgQueue.bytesCount() > maxQueueBytesSize))
+ {
+ setFollowing(false);
+ msgQueue.removeFirst();
+ }
+ }
+
+ if (isSaturated(update.getChangeNumber(), sourceHandler))
+ {
+ sourceHandler.setSaturated(true);
+ }
+
+ }
+ /**
+ * Set the shut down flag to true and returns the previous value of the flag.
+ * @return The previous value of the shut down flag
+ */
+ public boolean engageShutdown()
+ {
+ // Use thread safe boolean
+ return shuttingDown.getAndSet(true);
+ }
+
+ /**
+ * Get an approximation of the delay by looking at the age of the oldest
+ * message that has not been sent to this server.
+ * This is an approximation because the age is calculated using the
+ * clock of the server where the replicationServer is currently running
+ * while it should be calculated using the clock of the server
+ * that originally processed the change.
+ *
+ * The approximation error is therefore the time difference between
+ *
+ * @return the approximate delay for the connected server.
+ */
+ public long getApproxDelay()
+ {
+ long olderUpdateTime = getOlderUpdateTime();
+ if (olderUpdateTime == 0)
+ return 0;
+
+ long currentTime = TimeThread.getTime();
+ return ((currentTime - olderUpdateTime) / 1000);
+ }
+
+ /**
+ * Get the age of the older change that has not yet been replicated
+ * to the server handled by this ServerHandler.
+ * @return The age if the older change has not yet been replicated
+ * to the server handled by this ServerHandler.
+ */
+ public Long getApproxFirstMissingDate()
+ {
+ Long result = (long) 0;
+
+ // Get the older CN received
+ ChangeNumber olderUpdateCN = getOlderUpdateCN();
+ if (olderUpdateCN != null)
+ {
+ // If not present in the local RS db,
+ // then approximate with the older update time
+ result = olderUpdateCN.getTime();
+ }
+ return result;
+ }
+
+ /**
+ * Returns the Replication Server Domain to which belongs this handler.
+ * @param createIfNotExist Creates the domain if it does not exist.
+ * @return The replication server domain.
+ */
+ public ReplicationServerDomain getDomain(boolean createIfNotExist)
+ {
+ if (replicationServerDomain==null)
+ {
+ replicationServerDomain =
+ replicationServer.getReplicationServerDomain(serviceId,createIfNotExist);
+ }
+ return replicationServerDomain;
+ }
+
+ /**
+ * Get the count of updates received from the server.
+ * @return the count of update received from the server.
+ */
+ public int getInCount()
+ {
+ return inCount;
+ }
+
+ /**
+ * Retrieves a set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is requested.
+ *
+ * @return A set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is
+ * requested.
+ */
+ @Override
+ public ArrayList<Attribute> getMonitorData()
+ {
+ ArrayList<Attribute> attributes = new ArrayList<Attribute>();
+ attributes.add(Attributes.create("handler", getMonitorInstanceName()));
+ attributes.add(
+ Attributes.create("queue-size", String.valueOf(msgQueue.count())));
+ attributes.add(
+ Attributes.create(
+ "queue-size-bytes", String.valueOf(msgQueue.bytesCount())));
+ attributes.add(
+ Attributes.create(
+ "following", String.valueOf(following)));
+ return attributes;
+ }
+
+ /**
+ * Retrieves the name of this monitor provider. It should be unique among all
+ * monitor providers, including all instances of the same monitor provider.
+ *
+ * @return The name of this monitor provider.
+ */
+ @Override
+ public String getMonitorInstanceName()
+ {
+ return "Message Handler";
+ }
+
+ /**
+ * Get the next update that must be sent to the consumer
+ * from the message queue or from the database.
+ *
+ * @param synchronous specifies what to do when the queue is empty.
+ * when true, the method blocks; when false the method return null.
+ * @return The next update that must be sent to the consumer.
+ * null when synchronous is false and queue is empty.
+ */
+ protected UpdateMsg getnextMessage(boolean synchronous)
+ {
+ UpdateMsg msg;
+ while (activeConsumer == true)
+ {
+ if (following == false)
+ {
+ /* this server is late with regard to some other masters
+ * in the topology or just joined the topology.
+ * In such cases, we can't keep all changes in the queue
+ * without saturating the memory, we therefore use
+ * a lateQueue that is filled with a few changes from the changelogDB
+ * If this server is able to close the gap, it will start using again
+ * the regular msgQueue later.
+ */
+ if (lateQueue.isEmpty())
+ {
+ /*
+ * Start from the server State
+ * Loop until the queue high mark or until no more changes
+ * for each known LDAP master
+ * get the next CSN after this last one :
+ * - try to get next from the file
+ * - if not found in the file
+ * - try to get the next from the queue
+ * select the smallest of changes
+ * check if it is in the memory tree
+ * yes : lock memory tree.
+ * check all changes from the list, remove the ones that
+ * are already sent
+ * unlock memory tree
+ * restart as usual
+ * load this change on the delayList
+ *
+ */
+ ReplicationIteratorComparator comparator =
+ new ReplicationIteratorComparator();
+ SortedSet<ReplicationIterator> iteratorSortedSet =
+ new TreeSet<ReplicationIterator>(comparator);
+ /* fill the lateQueue */
+ for (short serverId : replicationServerDomain.getServers())
+ {
+ ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
+ ReplicationIterator iterator =
+ replicationServerDomain.getChangelogIterator(serverId, lastCsn);
+ if (iterator != null)
+ {
+ if (iterator.getChange() != null)
+ {
+ iteratorSortedSet.add(iterator);
+ } else
+ {
+ iterator.releaseCursor();
+ }
+ }
+ }
+
+ // The loop below relies on the fact that it is sorted based
+ // on the currentChange of each iterator to consider the next
+ // change across all servers.
+ // Hence it is necessary to remove and eventual add again an iterator
+ // when looping in order to keep consistent the order of the
+ // iterators (see ReplicationIteratorComparator.
+ while (!iteratorSortedSet.isEmpty() &&
+ (lateQueue.count()<100) &&
+ (lateQueue.bytesCount()<50000) )
+ {
+ ReplicationIterator iterator = iteratorSortedSet.first();
+ iteratorSortedSet.remove(iterator);
+ lateQueue.add(iterator.getChange());
+ if (iterator.next())
+ iteratorSortedSet.add(iterator);
+ else
+ iterator.releaseCursor();
+ }
+ for (ReplicationIterator iterator : iteratorSortedSet)
+ {
+ iterator.releaseCursor();
+ }
+ /*
+ * Check if the first change in the lateQueue is also on the regular
+ * queue
+ */
+ if (lateQueue.isEmpty())
+ {
+ synchronized (msgQueue)
+ {
+ if ((msgQueue.count() < maxQueueSize) &&
+ (msgQueue.bytesCount() < maxQueueBytesSize))
+ {
+ setFollowing(true);
+ }
+ }
+ } else
+ {
+ msg = lateQueue.first();
+ synchronized (msgQueue)
+ {
+ if (msgQueue.contains(msg))
+ {
+ /* we finally catch up with the regular queue */
+ setFollowing(true);
+ lateQueue.clear();
+ UpdateMsg msg1;
+ do
+ {
+ msg1 = msgQueue.removeFirst();
+ } while (!msg.getChangeNumber().equals(msg1.getChangeNumber()));
+ this.updateServerState(msg);
+ return msg;
+ }
+ }
+ }
+ } else
+ {
+ /* get the next change from the lateQueue */
+ msg = lateQueue.removeFirst();
+ this.updateServerState(msg);
+ return msg;
+ }
+ }
+ synchronized (msgQueue)
+ {
+ if (following == true)
+ {
+ try
+ {
+ while (msgQueue.isEmpty() && (following == true))
+ {
+ if (!synchronous)
+ return null;
+ msgQueue.wait(500);
+ if (!activeConsumer)
+ return null;
+ }
+ } catch (InterruptedException e)
+ {
+ return null;
+ }
+ msg = msgQueue.removeFirst();
+
+ if (this.updateServerState(msg))
+ {
+ /*
+ * Only push the message if it has not yet been seen
+ * by the other server.
+ * Otherwise just loop to select the next message.
+ */
+ return msg;
+ }
+ }
+ }
+ /*
+ * Need to loop because following flag may have gone to false between
+ * the first check at the beginning of this method
+ * and the second check just above.
+ */
+ }
+ return null;
+ }
+
+ /**
+ * Get the older Change Number for that server.
+ * Returns null when the queue is empty.
+ * @return The older change number.
+ */
+ public ChangeNumber getOlderUpdateCN()
+ {
+ ChangeNumber result = null;
+ synchronized (msgQueue)
+ {
+ if (isFollowing())
+ {
+ if (msgQueue.isEmpty())
+ {
+ result = null;
+ } else
+ {
+ UpdateMsg msg = msgQueue.first();
+ result = msg.getChangeNumber();
+ }
+ } else
+ {
+ if (lateQueue.isEmpty())
+ {
+ // isFollowing is false AND lateQueue is empty
+ // We may be at the very moment when the writer has emptyed the
+ // lateQueue when it sent the last update. The writer will fill again
+ // the lateQueue when it will send the next update but we are not yet
+ // there. So let's take the last change not sent directly from
+ // the db.
+
+ ReplicationIteratorComparator comparator =
+ new ReplicationIteratorComparator();
+ SortedSet<ReplicationIterator> iteratorSortedSet =
+ new TreeSet<ReplicationIterator>(comparator);
+ try
+ {
+ // Build a list of candidates iterator (i.e. db i.e. server)
+ for (short serverId : replicationServerDomain.getServers())
+ {
+ // get the last already sent CN from that server
+ ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
+ // get an iterator in this server db from that last change
+ ReplicationIterator iterator =
+ replicationServerDomain.getChangelogIterator(serverId, lastCsn);
+ // if that iterator has changes, then it is a candidate
+ // it is added in the sorted list at a position given by its
+ // current change (see ReplicationIteratorComparator).
+ if ((iterator != null) && (iterator.getChange() != null))
+ {
+ iteratorSortedSet.add(iterator);
+ }
+ }
+ UpdateMsg msg = iteratorSortedSet.first().getChange();
+ result = msg.getChangeNumber();
+ } catch (Exception e)
+ {
+ result = null;
+ } finally
+ {
+ for (ReplicationIterator iterator : iteratorSortedSet)
+ {
+ iterator.releaseCursor();
+ }
+ }
+ } else
+ {
+ UpdateMsg msg = lateQueue.first();
+ result = msg.getChangeNumber();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get the older update time for that server.
+ * @return The older update time.
+ */
+ public long getOlderUpdateTime()
+ {
+ ChangeNumber olderUpdateCN = getOlderUpdateCN();
+ if (olderUpdateCN == null)
+ return 0;
+ return olderUpdateCN.getTime();
+ }
+
+ /**
+ * Get the count of updates sent to this server.
+ * @return The count of update sent to this server.
+ */
+ public int getOutCount()
+ {
+ return outCount;
+ }
+
+ /**
+ * Get the number of message in the receive message queue.
+ * @return Size of the receive message queue.
+ */
+ public int getRcvMsgQueueSize()
+ {
+ synchronized (msgQueue)
+ {
+ /*
+ * When the server is up to date or close to be up to date,
+ * the number of updates to be sent is the size of the receive queue.
+ */
+ if (isFollowing())
+ return msgQueue.count();
+ else
+ {
+ /**
+ * When the server is not able to follow, the msgQueue
+ * may become too large and therefore won't contain all the
+ * changes. Some changes may only be stored in the backing DB
+ * of the servers.
+ * The total size of the receive queue is calculated by doing
+ * the sum of the number of missing changes for every dbHandler.
+ */
+ ServerState dbState = replicationServerDomain.getDbServerState();
+ return ServerState.diffChanges(dbState, serverState);
+ }
+ }
+ }
+
+ /**
+ * Get the state of this server.
+ *
+ * @return ServerState the state for this server..
+ */
+ public ServerState getServerState()
+ {
+ return serverState;
+ }
+
+ /**
+ * Get the name of the serviceId (usually baseDn) for this handler.
+ * @return The name of the serviceId.
+ */
+ protected String getServiceId()
+ {
+ return serviceId;
+ }
+
+ /**
+ * Retrieves the length of time in milliseconds that should elapse between
+ * calls to the <CODE>updateMonitorData()</CODE> method. A negative or zero
+ * return value indicates that the <CODE>updateMonitorData()</CODE> method
+ * should not be periodically invoked.
+ *
+ * @return The length of time in milliseconds that should elapse between
+ * calls to the <CODE>updateMonitorData()</CODE> method.
+ */
+ @Override
+ public long getUpdateInterval()
+ {
+ /* we don't wont to do polling on this monitor */
+ return 0;
+ }
+
+ /**
+ * Increase the counter of update received from the server.
+ */
+ public void incrementInCount()
+ {
+ inCount++;
+ }
+
+ /**
+ * Increase the counter of updates sent to the server.
+ */
+ public void incrementOutCount()
+ {
+ outCount++;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeMonitorProvider(MonitorProviderCfg configuration)
+ throws ConfigException, InitializationException
+ {
+ // Nothing to do for now
+ }
+
+ /**
+ * Check if the LDAP server can follow the speed of the other servers.
+ * @return true when the server has all the not yet sent changes
+ * in its queue.
+ */
+ public boolean isFollowing()
+ {
+ return following;
+ }
+
+ /**
+ * Check is this server is saturated (this server has already been
+ * sent a bunch of updates and has not processed them so they are staying
+ * in the message queue for this server an the size of the queue
+ * for this server is above the configured limit.
+ *
+ * The limit can be defined in number of updates or with a maximum delay
+ *
+ * @param changeNumber The changenumber to use to make the delay calculations.
+ * @param sourceHandler The ServerHandler which is sending the update.
+ * @return true is saturated false if not saturated.
+ */
+ public boolean isSaturated(ChangeNumber changeNumber,
+ MessageHandler sourceHandler)
+ {
+ synchronized (msgQueue)
+ {
+ int size = msgQueue.count();
+
+ if ((maxReceiveQueue > 0) && (size >= maxReceiveQueue))
+ return true;
+
+ if ((sourceHandler.maxSendQueue > 0) &&
+ (size >= sourceHandler.maxSendQueue))
+ return true;
+
+ if (!msgQueue.isEmpty())
+ {
+ UpdateMsg firstUpdate = msgQueue.first();
+
+ if (firstUpdate != null)
+ {
+ long timeDiff = changeNumber.getTimeSec() -
+ firstUpdate.getChangeNumber().getTimeSec();
+
+ if ((maxReceiveDelay > 0) && (timeDiff >= maxReceiveDelay))
+ return true;
+
+ if ((sourceHandler.maxSendDelay > 0) &&
+ (timeDiff >= sourceHandler.maxSendDelay))
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Check that the size of the Server Handler messages Queue has lowered
+ * below the limit and therefore allowing the reception of messages
+ * from other servers to restart.
+ * @param source The ServerHandler which was sending the update.
+ * can be null.
+ * @return true if the processing can restart
+ */
+ public boolean restartAfterSaturation(MessageHandler source)
+ {
+ synchronized (msgQueue)
+ {
+ int queueSize = msgQueue.count();
+ if ((maxReceiveQueue > 0) && (queueSize >= restartReceiveQueue))
+ return false;
+ if ((source != null) && (source.maxSendQueue > 0) &&
+ (queueSize >= source.restartSendQueue))
+ return false;
+
+ if (!msgQueue.isEmpty())
+ {
+ UpdateMsg firstUpdate = msgQueue.first();
+ UpdateMsg lastUpdate = msgQueue.last();
+
+ if ((firstUpdate != null) && (lastUpdate != null))
+ {
+ long timeDiff = lastUpdate.getChangeNumber().getTimeSec() -
+ firstUpdate.getChangeNumber().getTimeSec();
+ if ((maxReceiveDelay > 0) && (timeDiff >= restartReceiveDelay))
+ return false;
+ if ((source != null) && (source.maxSendDelay > 0) && (timeDiff >=
+ source.restartSendDelay))
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Set that the consumer is now becoming inactive and thus getNextMessage
+ * should not return any UpdateMsg any more.
+ * @param active the provided state of the consumer.
+ */
+ public void setConsumerActive(boolean active)
+ {
+ this.activeConsumer = active;
+ }
+
+ /**
+ * Set the following flag of this server.
+ * @param following the value that should be set.
+ */
+ private void setFollowing(boolean following)
+ {
+ this.following = following;
+ }
+
+ private void setSaturated(boolean value)
+ {
+ flowControl = value;
+ }
+
+ /**
+ * Set the initial value of the serverState for this handler.
+ * Expected to be done once, then the state will be updated using
+ * updateServerState() method.
+ * @param serverState the provided serverState.
+ * @exception DirectoryException raised when a problem occurs.
+ */
+ public void setInitialServerState(ServerState serverState)
+ throws DirectoryException
+ {
+ this.serverState = serverState;
+ }
+
+
+ /**
+ * Set the serviceId (usually baseDn) for this handler. Expected to be done
+ * once and never changed during the handler life.
+ * @param serviceId The provided serviceId.
+ * @exception DirectoryException raised when a problem occurs.
+ */
+ protected void setServiceIdAndDomain(String serviceId)
+ throws DirectoryException
+ {
+ if (this.serviceId != null)
+ {
+ if (!this.serviceId.equalsIgnoreCase(serviceId))
+ {
+ Message message = ERR_RS_DN_DOES_NOT_MATCH.get(
+ this.serviceId.toString(),
+ serviceId.toString());
+ throw new DirectoryException(ResultCode.OTHER,
+ message, null);
+ }
+ }
+ else
+ {
+ this.serviceId = serviceId;
+ this.replicationServerDomain = getDomain(true);
+ }
+ }
+
+ /**
+ * Shutdown this handler.
+ */
+ public void shutdown()
+ {
+ /*
+ * Shutdown ServerWriter
+ */
+ synchronized (msgQueue)
+ {
+ msgQueue.clear();
+ msgQueue.notify();
+ msgQueue.notifyAll();
+ }
+
+ DirectoryServer.deregisterMonitorProvider(getMonitorInstanceName());
+ }
+
+ /**
+ * Performs any processing periodic processing that may be desired to update
+ * the information associated with this monitor. Note that best-effort
+ * attempts will be made to ensure that calls to this method come
+ * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
+ * be made.
+ */
+ @Override
+ public void updateMonitorData()
+ {
+ // As long as getUpdateInterval() returns 0, this will never get called
+ }
+
+ /**
+ * Update the serverState with the last message sent.
+ *
+ * @param msg the last update sent.
+ * @return boolean indicating if the update was meaningful.
+ */
+ public boolean updateServerState(UpdateMsg msg)
+ {
+ return serverState.update(msg.getChangeNumber());
+ }
+
+ /**
+ * Get the groupId of the hosting RS.
+ * @return the group id.
+ */
+ public byte getLocalGroupId()
+ {
+ return replicationServer.getGroupId();
+ }
+
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
index 348680a..8c21f77 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
@@ -42,16 +42,18 @@
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import org.opends.messages.Category;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
+import org.opends.messages.Severity;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.ReplicationServerCfg;
import org.opends.server.api.Backend;
@@ -61,13 +63,22 @@
import org.opends.server.api.RestoreTaskListener;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.WorkflowImpl;
+import org.opends.server.core.networkgroups.NetworkGroup;
import org.opends.server.loggers.LogLevel;
import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.ExternalChangeLogSession;
import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.ReplServerStartMsg;
import org.opends.server.replication.protocol.ReplSessionSecurity;
+import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.ServerStartECLMsg;
+import org.opends.server.replication.protocol.ServerStartMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.LDIFExportConfig;
@@ -75,7 +86,9 @@
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.util.LDIFReader;
+import org.opends.server.util.ServerConstants;
import org.opends.server.util.TimeThread;
+import org.opends.server.workflowelement.externalchangelog.ECLWorkflowElement;
import com.sleepycat.je.DatabaseException;
@@ -155,6 +168,8 @@
*/
private static final DebugTracer TRACER = getTracer();
+ private String externalChangeLogWorkflowID = "External Changelog Workflow ID";
+ ECLWorkflowElement eclwe;
private static HashSet<Integer> localPorts = new HashSet<Integer>();
/**
@@ -267,9 +282,35 @@
ReplSessionSecurity.HANDSHAKE_TIMEOUT);
if (session == null) // Error, go back to accept
continue;
- ServerHandler handler = new ServerHandler(session, queueSize);
- handler.start(null, serverId, serverURL, rcvWindow,
- false, this);
+
+ ReplicationMsg msg = session.receive();
+
+ if (msg instanceof ServerStartMsg)
+ {
+ DataServerHandler handler = new DataServerHandler(session,
+ queueSize,serverURL,serverId,this,rcvWindow);
+ handler.startFromRemoteDS((ServerStartMsg)msg);
+ }
+ else if (msg instanceof ReplServerStartMsg)
+ {
+ ReplicationServerHandler handler = new ReplicationServerHandler(
+ session,queueSize,serverURL,serverId,this,rcvWindow);
+ handler.startFromRemoteRS((ReplServerStartMsg)msg);
+ }
+ else if (msg instanceof ServerStartECLMsg)
+ {
+ ECLServerHandler handler = new ECLServerHandler(
+ session,queueSize,serverURL,serverId,this,rcvWindow);
+ handler.startFromRemoteServer((ServerStartECLMsg)msg);
+ }
+ else
+ {
+ // We did not recognize the message, close session as what
+ // can happen after is undetermined and we do not want the server to
+ // be disturbed
+ ServerHandler.closeSession(session, null, null);
+ return;
+ }
}
catch (Exception e)
{
@@ -277,6 +318,10 @@
// shutdown or changing the port number process.
// Just log debug information and loop.
// Do not log the message during shutdown.
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
if (shutdown == false) {
Message message =
ERR_EXCEPTION_LISTENING.get(e.getLocalizedMessage());
@@ -379,20 +424,20 @@
/**
* Establish a connection to the server with the address and port.
*
- * @param serverURL The address and port for the server, separated by a
+ * @param remoteServerURL The address and port for the server, separated by a
* colon.
* @param baseDn The baseDn of the connection
*/
- private void connect(String serverURL, String baseDn)
+ private void connect(String remoteServerURL, String baseDn)
{
- int separator = serverURL.lastIndexOf(':');
- String port = serverURL.substring(separator + 1);
- String hostname = serverURL.substring(0, separator);
- boolean sslEncryption = replSessionSecurity.isSslEncryption(serverURL);
+ int separator = remoteServerURL.lastIndexOf(':');
+ String port = remoteServerURL.substring(separator + 1);
+ String hostname = remoteServerURL.substring(0, separator);
+ boolean sslEncryption =replSessionSecurity.isSslEncryption(remoteServerURL);
if (debugEnabled())
TRACER.debugInfo("RS " + this.getMonitorInstanceName() +
- " connects to " + serverURL);
+ " connects to " + remoteServerURL);
try
{
@@ -402,12 +447,25 @@
socket.setTcpNoDelay(true);
socket.connect(ServerAddr, 500);
+ /*
ServerHandler handler = new ServerHandler(
replSessionSecurity.createClientSession(serverURL, socket,
ReplSessionSecurity.HANDSHAKE_TIMEOUT),
queueSize);
handler.start(baseDn, serverId, this.serverURL, rcvWindow,
sslEncryption, this);
+ */
+
+ ReplicationServerHandler handler = new ReplicationServerHandler(
+ replSessionSecurity.createClientSession(remoteServerURL,
+ socket,
+ ReplSessionSecurity.HANDSHAKE_TIMEOUT),
+ queueSize,
+ this.serverURL,
+ serverId,
+ this,
+ rcvWindow);
+ handler.connect(baseDn, sslEncryption);
}
catch (Exception e)
{
@@ -470,6 +528,10 @@
serverId , this);
listenThread.start();
+ // Initialize the External Changelog
+ // FIXME: how is WF creation enabed/disabled in the RS ?
+ initializeECL();
+
if (debugEnabled())
TRACER.debugInfo("RS " +getMonitorInstanceName()+
" successfully initialized");
@@ -493,10 +555,88 @@
Message message =
ERR_COULD_NOT_BIND_CHANGELOG.get(changelogPort, e.getMessage());
logError(message);
+ } catch (DirectoryException e)
+ {
+ //FIXME:DirectoryException is raised by initializeECL => fix err msg
+ Message message = Message.raw(Category.SYNC, Severity.SEVERE_ERROR,
+ "Directory Exception raised by ECL initialization: " + e.getMessage());
+ logError(message);
}
}
/**
+ * Initializes the ECL access by creating a dedicated workflow element.
+ * @throws DirectoryException
+ */
+ private void initializeECL()
+ throws DirectoryException
+ {
+ WorkflowImpl externalChangeLogWorkflow;
+ if (WorkflowImpl.getWorkflow(externalChangeLogWorkflowID)
+ !=null)
+ return;
+
+ ECLWorkflowElement eclwe = new ECLWorkflowElement(this);
+
+ // Create the workflow for the base DN and register the workflow with
+ // the server.
+ externalChangeLogWorkflow = new WorkflowImpl(
+ externalChangeLogWorkflowID,
+ DN.decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT),
+ eclwe.getWorkflowElementID(),
+ eclwe);
+ externalChangeLogWorkflow.register();
+
+ NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
+ defaultNetworkGroup.registerWorkflow(externalChangeLogWorkflow);
+
+ // FIXME:ECL should the ECL Workflow be registered in adminNetworkGroup?
+ NetworkGroup adminNetworkGroup = NetworkGroup.getAdminNetworkGroup();
+ adminNetworkGroup.registerWorkflow(externalChangeLogWorkflow);
+
+ // FIXME:ECL should the ECL Workflow be registered in internalNetworkGroup?
+ NetworkGroup internalNetworkGroup = NetworkGroup.getInternalNetworkGroup();
+ internalNetworkGroup.registerWorkflow(externalChangeLogWorkflow);
+
+}
+
+ private void finalizeECL()
+ {
+ WorkflowImpl eclwf =
+ (WorkflowImpl)WorkflowImpl.getWorkflow(externalChangeLogWorkflowID);
+
+ // do it only if not already done by another RS (unit test case)
+ // if (DirectoryServer.getWorkflowElement(externalChangeLogWorkflowID)
+ if (eclwf!=null)
+ {
+
+
+ // FIXME:ECL should the ECL Workflow be registered in internalNetworkGroup?
+ NetworkGroup internalNetworkGroup = NetworkGroup.getInternalNetworkGroup();
+ internalNetworkGroup.deregisterWorkflow(externalChangeLogWorkflowID);
+
+ // FIXME:ECL should the ECL Workflow be registered in adminNetworkGroup?
+ NetworkGroup adminNetworkGroup = NetworkGroup.getAdminNetworkGroup();
+ adminNetworkGroup.deregisterWorkflow(externalChangeLogWorkflowID);
+
+ NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
+ defaultNetworkGroup.deregisterWorkflow(externalChangeLogWorkflowID);
+
+ eclwf.deregister();
+ eclwf.finalizeWorkflow();
+ }
+
+ eclwe = (ECLWorkflowElement)
+ DirectoryServer.getWorkflowElement("EXTERNAL CHANGE LOG");
+ if (eclwe!=null)
+ {
+ DirectoryServer.deregisterWorkflowElement(eclwe);
+ eclwe.finalizeWorkflowElement();
+ }
+
+}
+
+ /**
* Get the ReplicationServerDomain associated to the base DN given in
* parameter.
*
@@ -571,6 +711,8 @@
{
dbEnv.shutdown();
}
+
+ finalizeECL();
}
@@ -1220,6 +1362,38 @@
}
/**
+ * Returns the number of domains managed by this replication server.
+ * @return the number of domains managed.
+ */
+ public int getCacheSize()
+ {
+ return baseDNs.size();
+ }
+
+ /**
+ * Create a new session to get the ECL.
+ * @param msg The message that specifies the ECL request.
+ * @return Returns the created session.
+ * @throws DirectoryException When an error occurs.
+ */
+ public ExternalChangeLogSession createECLSession(StartECLSessionMsg msg)
+ throws DirectoryException
+ {
+ ExternalChangeLogSessionImpl session =
+ new ExternalChangeLogSessionImpl(this, msg);
+ return session;
+ }
+
+ /**
+ * Getter on the server URL.
+ * @return the server URL.
+ */
+ public String getServerURL()
+ {
+ return this.serverURL;
+ }
+
+ /**
* This method allows to check if the Replication Server given
* as the parameter is running in the local JVM.
*
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
index ea99e4b..76aa95b 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
@@ -26,56 +26,59 @@
*/
package org.opends.server.replication.server;
-import org.opends.messages.Message;
-import org.opends.messages.MessageBuilder;
-
-import static org.opends.server.loggers.debug.DebugLogger.*;
-
-import org.opends.server.admin.std.server.MonitorProviderCfg;
-import org.opends.server.api.MonitorProvider;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.loggers.debug.DebugTracer;
-import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
-import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantLock;
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.Severity;
+import org.opends.server.admin.std.server.MonitorProviderCfg;
+import org.opends.server.api.MonitorProvider;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.RSInfo;
import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.common.StatusMachineEvent;
+import org.opends.server.replication.protocol.AckMsg;
+import org.opends.server.replication.protocol.ChangeStatusMsg;
import org.opends.server.replication.protocol.ErrorMsg;
-import org.opends.server.replication.protocol.RoutableMsg;
-import org.opends.server.replication.protocol.UpdateMsg;
-import org.opends.server.replication.protocol.TopologyMsg;
import org.opends.server.replication.protocol.MonitorMsg;
import org.opends.server.replication.protocol.MonitorRequestMsg;
+import org.opends.server.replication.protocol.ProtocolVersion;
import org.opends.server.replication.protocol.ResetGenerationIdMsg;
+import org.opends.server.replication.protocol.RoutableMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.Attributes;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
import com.sleepycat.je.DatabaseException;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.locks.ReentrantLock;
-import org.opends.server.replication.common.AssuredMode;
-import org.opends.server.replication.common.DSInfo;
-import org.opends.server.replication.common.RSInfo;
-import org.opends.server.replication.common.ServerStatus;
-import org.opends.server.replication.common.StatusMachineEvent;
-import org.opends.server.replication.protocol.AckMsg;
-import org.opends.server.replication.protocol.ChangeStatusMsg;
-import org.opends.server.replication.protocol.ProtocolVersion;
/**
* This class define an in-memory cache that will be used to store
@@ -111,8 +114,8 @@
* to this replication server.
*
*/
- private final Map<Short, ServerHandler> directoryServers =
- new ConcurrentHashMap<Short, ServerHandler>();
+ private final Map<Short, DataServerHandler> directoryServers =
+ new ConcurrentHashMap<Short, DataServerHandler>();
/*
* This map contains one ServerHandler for each replication servers
@@ -123,8 +126,11 @@
* We add new TreeSet in the HashMap when a new replication server register
* to this replication server.
*/
- private final Map<Short, ServerHandler> replicationServers =
- new ConcurrentHashMap<Short, ServerHandler>();
+ private final Map<Short, ReplicationServerHandler> replicationServers =
+ new ConcurrentHashMap<Short, ReplicationServerHandler>();
+
+ private final ConcurrentLinkedQueue<MessageHandler> otherHandlers =
+ new ConcurrentLinkedQueue<MessageHandler>();
/*
* This map contains the List of updates received from each
@@ -134,16 +140,14 @@
new ConcurrentHashMap<Short, DbHandler>();
private ReplicationServer replicationServer;
- /* GenerationId management */
+ // GenerationId management
private long generationId = -1;
private boolean generationIdSavedStatus = false;
- /**
- * The tracer object for the debug logger.
- */
+
+ // The tracer object for the debug logger.
private static final DebugTracer TRACER = getTracer();
- /* Monitor data management */
-
+ // Monitor data management
/**
* The monitor data consolidated over the topology.
*/
@@ -346,9 +350,9 @@
/*
* Push the message to the replication servers
*/
- if (sourceHandler.isLDAPserver())
+ if (sourceHandler.isDataServer())
{
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
/**
* Ignore updates to RS with bad gen id
@@ -397,7 +401,7 @@
/*
* Push the message to the LDAP servers
*/
- for (ServerHandler handler : directoryServers.values())
+ for (DataServerHandler handler : directoryServers.values())
{
// Don't forward the change to the server that just sent it
if (handler == sourceHandler)
@@ -467,6 +471,14 @@
handler.add(update, sourceHandler);
}
}
+
+ // Push the message to the other subscribing handlers
+ Iterator<MessageHandler> otherIter = otherHandlers.iterator();
+ while (otherIter.hasNext())
+ {
+ MessageHandler handler = otherIter.next();
+ handler.add(update, sourceHandler);
+ }
}
/**
@@ -522,10 +534,10 @@
if (sourceGroupId == groupId)
// Assured feature does not cross different group ids
{
- if (sourceHandler.isLDAPserver())
+ if (sourceHandler.isDataServer())
{
// Look for RS eligible for assured
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
if (handler.getGroupId() == groupId)
// No ack expected from a RS with different group id
@@ -541,7 +553,7 @@
}
// Look for DS eligible for assured
- for (ServerHandler handler : directoryServers.values())
+ for (DataServerHandler handler : directoryServers.values())
{
// Don't forward the change to the server that just sent it
if (handler == sourceHandler)
@@ -636,7 +648,7 @@
(generationId == sourceHandler.getGenerationId()))
// Ignore assured updates from wrong generationId servers
{
- if (sourceHandler.isLDAPserver())
+ if (sourceHandler.isDataServer())
{
if (safeDataLevel == (byte) 1)
{
@@ -689,10 +701,10 @@
List<Short> expectedServers = new ArrayList<Short>();
if (interestedInAcks)
{
- if (sourceHandler.isLDAPserver())
+ if (sourceHandler.isDataServer())
{
// Look for RS eligible for assured
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
if (handler.getGroupId() == groupId)
// No ack expected from a RS with different group id
@@ -879,7 +891,7 @@
origServer.incrementAssuredSrReceivedUpdatesTimeout();
} else
{
- if (origServer.isLDAPserver())
+ if (origServer.isDataServer())
{
origServer.incrementAssuredSdReceivedUpdatesTimeout();
}
@@ -957,7 +969,7 @@
*/
public void stopReplicationServers(Collection<String> replServers)
{
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
if (replServers.contains(handler.getServerAddressURL()))
stopServer(handler);
@@ -970,13 +982,13 @@
public void stopAllServers()
{
// Close session with other replication servers
- for (ServerHandler serverHandler : replicationServers.values())
+ for (ReplicationServerHandler serverHandler : replicationServers.values())
{
stopServer(serverHandler);
}
// Close session with other LDAP servers
- for (ServerHandler serverHandler : directoryServers.values())
+ for (DataServerHandler serverHandler : directoryServers.values())
{
stopServer(serverHandler);
}
@@ -988,14 +1000,13 @@
* @param handler the DS we want to check
* @return true if this is not a duplicate server
*/
- public boolean checkForDuplicateDS(ServerHandler handler)
+ public boolean checkForDuplicateDS(DataServerHandler handler)
{
- ServerHandler oldHandler = directoryServers.get(handler.getServerId());
+ DataServerHandler oldHandler = directoryServers.get(handler.getServerId());
if (directoryServers.containsKey(handler.getServerId()))
{
// looks like two LDAP servers have the same serverId
- // log an error message and drop this connection.
Message message = ERR_DUPLICATE_SERVER_ID.get(
replicationServer.getMonitorInstanceName(), oldHandler.toString(),
handler.toString(), handler.getServerId());
@@ -1012,6 +1023,12 @@
*/
public void stopServer(ServerHandler handler)
{
+ if (debugEnabled())
+ TRACER.debugInfo(
+ "In RS " + this.replicationServer.getMonitorInstanceName() +
+ " domain=" + this +
+ " stopServer(SH)" + handler.getMonitorInstanceName() +
+ " " + stackTraceToSingleLineString(new Exception()));
/*
* We must prevent deadlock on replication server domain lock, when for
* instance this code is called from dying ServerReader but also dying
@@ -1020,14 +1037,103 @@
* or not (is already being processed or not).
*/
if (!handler.engageShutdown())
- // Only do this once (prevent other thread to enter here again)
+ // Only do this once (prevent other thread to enter here again)
{
- if (debugEnabled())
- TRACER.debugInfo(
- "In RS " + this.replicationServer.getMonitorInstanceName() +
- " for " + baseDn + " " +
- " stopServer " + handler.getMonitorInstanceName());
+ try
+ {
+ try
+ {
+ // Acquire lock on domain (see more details in comment of start()
+ // method of ServerHandler)
+ lock();
+ } catch (InterruptedException ex)
+ {
+ // Try doing job anyway...
+ }
+ if (handler.isReplicationServer())
+ {
+ if (replicationServers.containsValue(handler))
+ {
+ unregisterServerHandler(handler);
+ handler.shutdown();
+
+ // Check if generation id has to be resetted
+ mayResetGenerationId();
+ // Warn our DSs that a RS or DS has quit (does not use this
+ // handler as already removed from list)
+ buildAndSendTopoInfoToDSs(null);
+ }
+ } else
+ {
+ if (directoryServers.containsValue(handler))
+ {
+ // If this is the last DS for the domain,
+ // shutdown the status analyzer
+ if (directoryServers.size() == 1)
+ {
+ if (debugEnabled())
+ TRACER.debugInfo("In " +
+ replicationServer.getMonitorInstanceName() +
+ " remote server " + handler.getMonitorInstanceName() +
+ " is the last DS to be stopped: stopping status analyzer");
+ stopStatusAnalyzer();
+ }
+
+ unregisterServerHandler(handler);
+ handler.shutdown();
+
+ // Check if generation id has to be resetted
+ mayResetGenerationId();
+ // Update the remote replication servers with our list
+ // of connected LDAP servers
+ buildAndSendTopoInfoToRSs();
+ // Warn our DSs that a RS or DS has quit (does not use this
+ // handler as already removed from list)
+ buildAndSendTopoInfoToDSs(null);
+ }
+ else if (otherHandlers.contains(handler))
+ {
+ unRegisterHandler(handler);
+ handler.shutdown();
+ }
+ }
+
+ }
+ catch(Exception e)
+ {
+ logError(Message.raw(Category.SYNC, Severity.NOTICE,
+ stackTraceToSingleLineString(e)));
+ }
+ finally
+ {
+ release();
+ }
+ }
+ }
+
+ /**
+ * Stop the handler.
+ * @param handler The handler to stop.
+ */
+ public void stopServer(MessageHandler handler)
+ {
+ if (debugEnabled())
+ TRACER.debugInfo(
+ "In RS " + this.replicationServer.getMonitorInstanceName() +
+ " domain=" + this +
+ " stopServer(MH)" + handler.getMonitorInstanceName() +
+ " " + stackTraceToSingleLineString(new Exception()));
+ /*
+ * We must prevent deadlock on replication server domain lock, when for
+ * instance this code is called from dying ServerReader but also dying
+ * ServerWriter at the same time, or from a thread that wants to shut down
+ * the handler. So use a thread safe flag to know if the job must be done
+ * or not (is already being processed or not).
+ */
+ if (!handler.engageShutdown())
+ // Only do this once (prevent other thread to enter here again)
+ {
try
{
// Acquire lock on domain (see more details in comment of start()
@@ -1037,57 +1143,40 @@
{
// Try doing job anyway...
}
-
- if (handler.isReplicationServer())
+ if (otherHandlers.contains(handler))
{
- if (replicationServers.containsValue(handler))
- {
- replicationServers.remove(handler.getServerId());
- handler.shutdown();
-
- // Check if generation id has to be resetted
- mayResetGenerationId();
- // Warn our DSs that a RS or DS has quit (does not use this
- // handler as already removed from list)
- sendTopoInfoToDSs(null);
- }
- } else
- {
- if (directoryServers.containsValue(handler))
- {
- // If this is the last DS for the domain, shutdown the status analyzer
- if (directoryServers.size() == 1)
- {
- if (debugEnabled())
- TRACER.debugInfo("In " +
- replicationServer.getMonitorInstanceName() +
- " remote server " + handler.getMonitorInstanceName() +
- " is the last DS to be stopped: stopping status analyzer");
- stopStatusAnalyzer();
- }
-
- directoryServers.remove(handler.getServerId());
- handler.shutdown();
-
- // Check if generation id has to be resetted
- mayResetGenerationId();
- // Update the remote replication servers with our list
- // of connected LDAP servers
- sendTopoInfoToRSs();
- // Warn our DSs that a RS or DS has quit (does not use this
- // handler as already removed from list)
- sendTopoInfoToDSs(null);
- }
+ unRegisterHandler(handler);
+ handler.shutdown();
}
+ }
+ release();
+ }
- release();
+ /**
+ * Unregister this handler from the list of handlers registered to this
+ * domain.
+ * @param handler the provided handler to unregister.
+ */
+ protected void unregisterServerHandler(ServerHandler handler)
+ {
+ if (handler.isReplicationServer())
+ {
+ replicationServers.remove(handler.getServerId());
+ }
+ else
+ {
+ directoryServers.remove(handler.getServerId());
}
}
/**
- * Resets the generationId for this domain if there is no LDAP
- * server currently connected and if the generationId has never
- * been saved.
+ * This method resets the generationId for this domain if there is no LDAP
+ * server currently connected in the whole topology on this domain and
+ * if the generationId has never been saved.
+ *
+ * - test emtpyness of directoryServers list
+ * - traverse replicationServers list and test for each if DS are connected
+ * So it strongly relies on the directoryServers list
*/
protected void mayResetGenerationId()
{
@@ -1104,7 +1193,7 @@
boolean lDAPServersConnectedInTheTopology = false;
if (directoryServers.isEmpty())
{
- for (ServerHandler rsh : replicationServers.values())
+ for (ReplicationServerHandler rsh : replicationServers.values())
{
if (generationId != rsh.getGenerationId())
{
@@ -1149,14 +1238,16 @@
}
/**
- * Checks that a RS is not already connected.
- *
- * @param handler the RS we want to check
- * @return true if this is not a duplicate server
+ * Checks that a remote RS is not already connected to this hosting RS.
+ * @param handler The handler for the remote RS.
+ * @return flag specifying whether the remote RS is already connected.
+ * @throws DirectoryException when a problem occurs.
*/
- public boolean checkForDuplicateRS(ServerHandler handler)
+ public boolean checkForDuplicateRS(ReplicationServerHandler handler)
+ throws DirectoryException
{
- ServerHandler oldHandler = replicationServers.get(handler.getServerId());
+ ReplicationServerHandler oldHandler =
+ replicationServers.get(handler.getServerId());
if ((oldHandler != null))
{
if (oldHandler.getServerAddressURL().equals(
@@ -1166,7 +1257,9 @@
// have been sent at about the same time and 2 connections
// have been established.
// Silently drop this connection.
- } else
+ return false;
+ }
+ else
{
// looks like two replication servers have the same serverId
// log an error message and drop this connection.
@@ -1174,9 +1267,8 @@
replicationServer.getMonitorInstanceName(), oldHandler.
getServerAddressURL(), handler.getServerAddressURL(),
handler.getServerId());
- logError(message);
+ throw new DirectoryException(ResultCode.OTHER, message);
}
- return false;
}
return true;
}
@@ -1223,7 +1315,7 @@
{
LinkedHashSet<String> mySet = new LinkedHashSet<String>();
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
mySet.add(handler.getServerAddressURL());
}
@@ -1232,7 +1324,9 @@
}
/**
- * Return a Set containing the servers known by this replicationServer.
+ * Return a set containing the server that produced update and known by
+ * this replicationServer from all over the topology,
+ * whatever directly connected of connected to another RS.
* @return a set containing the servers known by this replicationServer.
*/
public Set<Short> getServers()
@@ -1250,7 +1344,7 @@
{
List<String> mySet = new ArrayList<String>(0);
- for (ServerHandler handler : directoryServers.values())
+ for (DataServerHandler handler : directoryServers.values())
{
mySet.add(String.valueOf(handler.getServerId()));
}
@@ -1348,7 +1442,7 @@
{
// Send to all replication servers with a least one remote
// server connected
- for (ServerHandler rsh : replicationServers.values())
+ for (ReplicationServerHandler rsh : replicationServers.values())
{
if (rsh.hasRemoteLDAPServers())
{
@@ -1358,7 +1452,7 @@
}
// Sends to all connected LDAP servers
- for (ServerHandler destinationHandler : directoryServers.values())
+ for (DataServerHandler destinationHandler : directoryServers.values())
{
// Don't loop on the sender
if (destinationHandler == senderHandler)
@@ -1368,7 +1462,7 @@
} else
{
// Destination is one server
- ServerHandler destinationHandler =
+ DataServerHandler destinationHandler =
directoryServers.get(msg.getDestination());
if (destinationHandler != null)
{
@@ -1378,9 +1472,9 @@
// the targeted server is NOT connected
// Let's search for THE changelog server that MAY
// have the targeted server connected.
- if (senderHandler.isLDAPserver())
+ if (senderHandler.isDataServer())
{
- for (ServerHandler h : replicationServers.values())
+ for (ReplicationServerHandler h : replicationServers.values())
{
// Send to all replication servers with a least one remote
// server connected
@@ -1421,7 +1515,7 @@
// build the full list of all servers in the topology
// and send back a MonitorMsg with the full list of all the servers
// in the topology.
- if (senderHandler.isLDAPserver())
+ if (senderHandler.isDataServer())
{
MonitorMsg returnMsg =
new MonitorMsg(msg.getDestination(), msg.getsenderID());
@@ -1481,7 +1575,7 @@
// from the states stored in the serverHandler.
// - the server state
// - the older missing change
- for (ServerHandler lsh : this.directoryServers.values())
+ for (DataServerHandler lsh : this.directoryServers.values())
{
monitorMsg.setServerState(
lsh.getServerId(),
@@ -1491,7 +1585,7 @@
}
// Same for the connected RS
- for (ServerHandler rsh : this.replicationServers.values())
+ for (ReplicationServerHandler rsh : this.replicationServers.values())
{
monitorMsg.setServerState(
rsh.getServerId(),
@@ -1657,12 +1751,12 @@
*/
public void checkAllSaturation() throws IOException
{
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
handler.checkWindow();
}
- for (ServerHandler handler : directoryServers.values())
+ for (DataServerHandler handler : directoryServers.values())
{
handler.checkWindow();
}
@@ -1675,15 +1769,15 @@
* @return true if the server can restart sending changes.
* false if the server can't restart sending changes.
*/
- public boolean restartAfterSaturation(ServerHandler sourceHandler)
+ public boolean restartAfterSaturation(MessageHandler sourceHandler)
{
- for (ServerHandler handler : replicationServers.values())
+ for (MessageHandler handler : replicationServers.values())
{
if (!handler.restartAfterSaturation(sourceHandler))
return false;
}
- for (ServerHandler handler : directoryServers.values())
+ for (MessageHandler handler : directoryServers.values())
{
if (!handler.restartAfterSaturation(sourceHandler))
return false;
@@ -1698,9 +1792,9 @@
* @param notThisOne If not null, the topology message will not be sent to
* this passed server.
*/
- public void sendTopoInfoToDSs(ServerHandler notThisOne)
+ public void buildAndSendTopoInfoToDSs(ServerHandler notThisOne)
{
- for (ServerHandler handler : directoryServers.values())
+ for (DataServerHandler handler : directoryServers.values())
{
if ((notThisOne == null) || // All DSs requested
((notThisOne != null) && (handler != notThisOne)))
@@ -1725,10 +1819,10 @@
* Send a TopologyMsg to all the connected replication servers
* in order to let them know our connected LDAP servers.
*/
- public void sendTopoInfoToRSs()
+ public void buildAndSendTopoInfoToRSs()
{
TopologyMsg topoMsg = createTopologyMsgForRS();
- for (ServerHandler handler : replicationServers.values())
+ for (ReplicationServerHandler handler : replicationServers.values())
{
try
{
@@ -1755,7 +1849,7 @@
List<DSInfo> dsInfos = new ArrayList<DSInfo>();
// Go through every DSs
- for (ServerHandler serverHandler : directoryServers.values())
+ for (DataServerHandler serverHandler : directoryServers.values())
{
dsInfos.add(serverHandler.toDSInfo());
}
@@ -1785,7 +1879,7 @@
List<RSInfo> rsInfos = new ArrayList<RSInfo>();
// Go through every DSs (except recipient of msg)
- for (ServerHandler serverHandler : directoryServers.values())
+ for (DataServerHandler serverHandler : directoryServers.values())
{
if (serverHandler.getServerId() == destDsId)
continue;
@@ -1799,7 +1893,7 @@
// Go through every peer RSs (and get their connected DSs), also add info
// for RSs
- for (ServerHandler serverHandler : replicationServers.values())
+ for (ReplicationServerHandler serverHandler : replicationServers.values())
{
// Put RS info
rsInfos.add(serverHandler.toRSInfo());
@@ -1840,12 +1934,6 @@
synchronized public long setGenerationId(long generationId,
boolean savedStatus)
{
- if (debugEnabled())
- TRACER.debugInfo(
- "In " + this.replicationServer.getMonitorInstanceName() +
- " baseDN=" + baseDn +
- " RCache.set GenerationId=" + generationId);
-
long oldGenerationId = this.generationId;
if (this.generationId != generationId)
@@ -1916,7 +2004,7 @@
// After we'll have sent the message , the remote RS will adopt
// the new genId
rsHandler.setGenerationId(newGenId);
- if (senderHandler.isLDAPserver())
+ if (senderHandler.isDataServer())
{
rsHandler.forwardGenerationIdToRS(genIdMsg);
}
@@ -1929,7 +2017,7 @@
// Change status of the connected DSs according to the requested new
// reference generation id
- for (ServerHandler dsHandler : directoryServers.values())
+ for (DataServerHandler dsHandler : directoryServers.values())
{
try
{
@@ -1948,8 +2036,8 @@
// (consecutive to reset gen id message), we prefer advertising once for
// all after changes (less packet sent), here at the end of the reset msg
// treatment.
- sendTopoInfoToDSs(null);
- sendTopoInfoToRSs();
+ buildAndSendTopoInfoToDSs(null);
+ buildAndSendTopoInfoToRSs();
Message message = NOTE_RESET_GENERATION_ID.get(baseDn.toString(),
Long.toString(newGenId));
@@ -1964,7 +2052,7 @@
* that changed his status.
* @param csMsg The message containing the new status
*/
- public void processNewStatus(ServerHandler senderHandler,
+ public void processNewStatus(DataServerHandler senderHandler,
ChangeStatusMsg csMsg)
{
if (debugEnabled())
@@ -1995,8 +2083,8 @@
}
// Update every peers (RS/DS) with topology changes
- sendTopoInfoToDSs(senderHandler);
- sendTopoInfoToRSs();
+ buildAndSendTopoInfoToDSs(senderHandler);
+ buildAndSendTopoInfoToRSs();
Message message = NOTE_DIRECTORY_SERVER_CHANGED_STATUS.get(
Short.toString(senderHandler.getServerId()),
@@ -2014,7 +2102,7 @@
* @param event The event to be used for new status computation
* @return True if we have been interrupted (must stop), false otherwise
*/
- public boolean changeStatusFromStatusAnalyzer(ServerHandler serverHandler,
+ public boolean changeStatusFromStatusAnalyzer(DataServerHandler serverHandler,
StatusMachineEvent event)
{
try
@@ -2066,8 +2154,8 @@
}
// Update every peers (RS/DS) with topology changes
- sendTopoInfoToDSs(serverHandler);
- sendTopoInfoToRSs();
+ buildAndSendTopoInfoToDSs(serverHandler);
+ buildAndSendTopoInfoToRSs();
release();
return false;
@@ -2169,10 +2257,12 @@
* @param allowResetGenId True for allowing to reset the generation id (
* when called after initial handshake)
* @throws IOException If an error occurred.
+ * @throws DirectoryException If an error occurred.
*/
- public void receiveTopoInfoFromRS(TopologyMsg topoMsg, ServerHandler handler,
+ public void receiveTopoInfoFromRS(TopologyMsg topoMsg,
+ ReplicationServerHandler handler,
boolean allowResetGenId)
- throws IOException
+ throws IOException, DirectoryException
{
if (debugEnabled())
{
@@ -2186,7 +2276,8 @@
{
// Acquire lock on domain (see more details in comment of start() method
// of ServerHandler)
- lock();
+ if (!hasLock())
+ lock();
} catch (InterruptedException ex)
{
// Try doing job anyway...
@@ -2228,7 +2319,7 @@
* Sends the currently known topology information to every connected
* DS we have.
*/
- sendTopoInfoToDSs(null);
+ buildAndSendTopoInfoToDSs(null);
release();
}
@@ -2441,7 +2532,7 @@
{
// this is the latency of the remote RSi regarding another RSj
// let's update the latency of the LSes connected to RSj
- ServerHandler rsjHdr = replicationServers.get(rsid);
+ ReplicationServerHandler rsjHdr = replicationServers.get(rsid);
if (rsjHdr != null)
{
for (short remotelsid : rsjHdr.getConnectedDirectoryServerIds())
@@ -2496,7 +2587,7 @@
* Get the map of connected DSs.
* @return The map of connected DSs
*/
- public Map<Short, ServerHandler> getConnectedDSs()
+ public Map<Short, DataServerHandler> getConnectedDSs()
{
return directoryServers;
}
@@ -2505,7 +2596,7 @@
* Get the map of connected RSs.
* @return The map of connected RSs
*/
- public Map<Short, ServerHandler> getConnectedRSs()
+ public Map<Short, ReplicationServerHandler> getConnectedRSs()
{
return replicationServers;
}
@@ -2708,5 +2799,140 @@
return attributes;
}
-}
+ /**
+ * Register in the domain an handler that subscribes to changes.
+ * @param handler the provided subscribing handler.
+ */
+ public void registerHandler(MessageHandler handler)
+ {
+ this.otherHandlers.add(handler);
+ }
+
+ /**
+ * Unregister from the domain an handler.
+ * @param handler the provided unsubscribing handler.
+ * @return Whether this handler has been unregistered with success.
+ */
+ public boolean unRegisterHandler(MessageHandler handler)
+ {
+ return this.otherHandlers.remove(handler);
+ }
+
+ /**
+ * Return the state that contain for each server the time of eligibility.
+ * @return the state.
+ */
+ public ServerState getHeartbeatState()
+ {
+ // TODO:ECL Eligility must be supported
+ return this.getDbServerState();
+ }
+ /**
+ * Computes the change number eligible to the ECL.
+ * @return null if the domain does not play in eligibility.
+ */
+ public ChangeNumber computeEligibleCN()
+ {
+ ChangeNumber elligibleCN = null;
+ ServerState heartbeatState = getHeartbeatState();
+
+ if (heartbeatState==null)
+ return null;
+
+ // compute elligible CN
+ ServerState hbState = heartbeatState.duplicate();
+
+ Iterator<Short> it = hbState.iterator();
+ while (it.hasNext())
+ {
+ short sid = it.next();
+ ChangeNumber storedCN = hbState.getMaxChangeNumber(sid);
+
+ // If the most recent UpdateMsg or CLHeartbeatMsg received is very old
+ // then the server is considered down and not considered for eligibility
+ if (TimeThread.getTime()-storedCN.getTime()>2000)
+ {
+ if (debugEnabled())
+ TRACER.debugInfo(
+ "For RSD." + this.baseDn + " Server " + sid
+ + " is not considered for eligibility ... potentially down");
+ continue;
+ }
+
+ if ((elligibleCN == null) || (storedCN.older(elligibleCN)))
+ {
+ elligibleCN = storedCN;
+ }
+ }
+
+ if (debugEnabled())
+ TRACER.debugInfo(
+ "For RSD." + this.baseDn + " ElligibleCN()=" + elligibleCN);
+ return elligibleCN;
+ }
+
+ /**
+ * Computes the eligible server state by minimizing the dbServerState and the
+ * elligibleCN.
+ * @return The computed eligible server state.
+ */
+ public ServerState getCLElligibleState()
+ {
+ // ChangeNumber elligibleCN = computeEligibleCN();
+ ServerState res = new ServerState();
+ ServerState dbState = this.getDbServerState();
+ res = dbState;
+
+ /* TODO:ECL Eligibility is not yet implemented
+ Iterator<Short> it = dbState.iterator();
+ while (it.hasNext())
+ {
+ Short sid = it.next();
+ DbHandler h = sourceDbHandlers.get(sid);
+ ChangeNumber dbCN = dbState.getMaxChangeNumber(sid);
+ try
+ {
+ if ((elligibleCN!=null)&&(elligibleCN.older(dbCN)))
+ {
+ // some CN exist in the db newer than elligible CN
+ ReplicationIterator ri = h.generateIterator(elligibleCN);
+ ChangeNumber newCN = ri.getCurrentCN();
+ res.update(newCN);
+ ri.releaseCursor();
+ }
+ else
+ {
+ // no CN exist in the db newer than elligible CN
+ res.update(dbCN);
+ }
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+ */
+
+ if (debugEnabled())
+ TRACER.debugInfo("In " + this.getName()
+ + " getCLElligibleState returns:" + res);
+ return res;
+ }
+
+ /**
+ * Returns the start state of the domain, made of the first (oldest)
+ * change stored for each serverId.
+ * @return t start state of the domain.
+ */
+ public ServerState getStartState()
+ {
+ ServerState domainStartState = new ServerState();
+ Iterator<Short> it = this.getDbServerState().iterator();
+ for (DbHandler dbHandler : sourceDbHandlers.values())
+ {
+ domainStartState.update(dbHandler.getFirstChange());
+ }
+ return domainStartState;
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java
new file mode 100644
index 0000000..7b85671
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ReplicationServerHandler.java
@@ -0,0 +1,791 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.server;
+
+import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.opends.messages.Message;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.RSInfo;
+import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
+import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.ProtocolVersion;
+import org.opends.server.replication.protocol.ReplServerStartMsg;
+import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeBuilder;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.ResultCode;
+
+/**
+ * This class defines a server handler, which handles all interaction with a
+ * peer replication server.
+ */
+public class ReplicationServerHandler extends ServerHandler
+{
+
+ /*
+ * Properties filled only if remote server is a RS
+ */
+ private String serverAddressURL;
+ /**
+ * this collection will contain as many elements as there are
+ * LDAP servers connected to the remote replication server.
+ */
+ private final Map<Short, LightweightServerHandler> remoteDirectoryServers =
+ new ConcurrentHashMap<Short, LightweightServerHandler>();
+
+ /**
+ * Starts this handler based on a start message received from remote server.
+ * @param inReplServerStartMsg The start msg provided by the remote server.
+ * @return Whether the remote server requires encryption or not.
+ * @throws DirectoryException When a problem occurs.
+ */
+ public boolean processStartFromRemote(ReplServerStartMsg inReplServerStartMsg)
+ throws DirectoryException
+ {
+ try
+ {
+ protocolVersion = ProtocolVersion.minWithCurrent(
+ inReplServerStartMsg.getVersion());
+ generationId = inReplServerStartMsg.getGenerationId();
+ serverId = inReplServerStartMsg.getServerId();
+ serverURL = inReplServerStartMsg.getServerURL();
+ int separator = serverURL.lastIndexOf(':');
+ serverAddressURL =
+ session.getRemoteAddress() + ":" + serverURL.substring(separator +
+ 1);
+ setServiceIdAndDomain(inReplServerStartMsg.getBaseDn());
+ setInitialServerState(inReplServerStartMsg.getServerState());
+ setSendWindowSize(inReplServerStartMsg.getWindowSize());
+ if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+ {
+ // We support connection from a V1 RS
+ // Only V2 protocol has the group id in repl server start message
+ this.groupId = inReplServerStartMsg.getGroupId();
+ }
+
+ oldGenerationId = -100;
+
+ // Duplicate server ?
+ if (!replicationServerDomain.checkForDuplicateRS(this))
+ {
+ abortStart(null);
+ return false;
+ }
+ }
+ catch(Exception e)
+ {
+ Message message = Message.raw(e.getLocalizedMessage());
+ throw new DirectoryException(ResultCode.OTHER, message);
+ }
+ return inReplServerStartMsg.getSSLEncryption();
+ }
+
+ /**
+ * Creates a new handler object to a remote replication server.
+ * @param session The session with the remote RS.
+ * @param queueSize The queue size to manage updates to that RS.
+ * @param replicationServerURL The hosting local RS URL.
+ * @param replicationServerId The hosting local RS serverId.
+ * @param replicationServer The hosting local RS object.
+ * @param rcvWindowSize The receiving window size.
+ */
+ public ReplicationServerHandler(
+ ProtocolSession session,
+ int queueSize,
+ String replicationServerURL,
+ short replicationServerId,
+ ReplicationServer replicationServer,
+ int rcvWindowSize)
+ {
+ super(session, queueSize, replicationServerURL, replicationServerId,
+ replicationServer, rcvWindowSize);
+ }
+
+ /**
+ * Connect the hosting RS to the RS represented by THIS handler
+ * on an outgoing connection.
+ * @param serviceId The serviceId (usually baseDn).
+ * @param sslEncryption The sslEncryption requested to the remote RS.
+ * @throws DirectoryException when an error occurs.
+ */
+ public void connect(String serviceId, boolean sslEncryption)
+ throws DirectoryException
+ {
+
+ //
+ // the encryption we will request to the peer as we are the session creator
+ this.initSslEncryption = sslEncryption;
+
+ setServiceIdAndDomain(serviceId);
+
+ localGenerationId = replicationServerDomain.getGenerationId();
+ oldGenerationId = localGenerationId;
+
+ try
+ {
+ //
+ lockDomain(false); // notimeout
+
+ // we are the initiator and decides of the encryption
+ boolean sessionInitiatorSSLEncryption = this.initSslEncryption;
+
+ // Send start
+ ReplServerStartMsg outReplServerStartMsg = sendStartToRemote((short)-1);
+
+ // Wait answer
+ ReplicationMsg msg = session.receive();
+
+ // Reject bad responses
+ if (!(msg instanceof ReplServerStartMsg))
+ {
+ Message message = ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
+ msg.getClass().getCanonicalName(),
+ "ReplServerStartMsg");
+ abortStart(message);
+ return;
+ }
+
+ // Process hello from remote
+ processStartFromRemote((ReplServerStartMsg)msg);
+
+ // Log
+ logStartHandshakeSNDandRCV(outReplServerStartMsg,(ReplServerStartMsg)msg);
+
+ // Until here session is encrypted then it depends on the negociation
+ // The session initiator decides whether to use SSL.
+ if (!sessionInitiatorSSLEncryption)
+ session.stopEncryption();
+
+ if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+ {
+ // Only protocol version above V1 has a phase 2 handshake
+
+ // NOW PROCEDE WITH SECOND PHASE OF HANDSHAKE:
+ // TopologyMsg then TopologyMsg (with a RS)
+
+ // Send our own TopologyMsg to remote RS
+ TopologyMsg outTopoMsg = sendTopoToRemoteRS();
+
+ // wait and process Topo from remote RS
+ TopologyMsg inTopoMsg = waitAndProcessTopoFromRemoteRS();
+
+ logTopoHandshakeSNDandRCV(outTopoMsg, inTopoMsg);
+
+ // FIXME: i think this should be done for all protocol version !!
+ // not only those > V1
+ registerIntoDomain();
+
+ // Process TopologyMsg sent by remote RS: store matching new info
+ // (this will also warn our connected DSs of the new received info)
+ replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
+ }
+ super.finalizeStart();
+
+ }
+ catch(IOException ioe)
+ {
+ // FIXME receive
+ }
+ // catch(DirectoryException de)
+ //{ already logged
+ //
+ catch(Exception e)
+ {
+ // FIXME more detailed exceptions
+ }
+ finally
+ {
+ // Release domain
+ if ((replicationServerDomain != null) &&
+ replicationServerDomain.hasLock())
+ replicationServerDomain.release();
+ }
+ }
+
+ /**
+ * Starts the handler from a remote ReplServerStart message received from
+ * the remote replication server.
+ * @param inReplServerStartMsg The provided ReplServerStart message received.
+ */
+ public void startFromRemoteRS(ReplServerStartMsg inReplServerStartMsg)
+ {
+ //
+ localGenerationId = -1;
+ oldGenerationId = -100;
+ try
+ {
+ // Process start from remote
+ boolean sessionInitiatorSSLEncryption =
+ processStartFromRemote(inReplServerStartMsg);
+
+ // lock with timeout
+ lockDomain(true);
+
+ short reqVersion = -1;
+ if (protocolVersion == ProtocolVersion.REPLICATION_PROTOCOL_V1)
+ {
+ // We support connection from a V1 RS, send PDU with V1 form
+ reqVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1;
+ }
+
+ // send start to remote
+ ReplServerStartMsg outReplServerStartMsg = sendStartToRemote(reqVersion);
+
+ // log
+ logStartHandshakeRCVandSND(inReplServerStartMsg, outReplServerStartMsg);
+
+ // until here session is encrypted then it depends on the negociation
+ // The session initiator decides whether to use SSL.
+ if (!sessionInitiatorSSLEncryption)
+ session.stopEncryption();
+
+ TopologyMsg inTopoMsg = null;
+ if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+ {
+ // Only protocol version above V1 has a phase 2 handshake
+ // NOW PROCEDE WITH SECOND PHASE OF HANDSHAKE:
+ // TopologyMsg then TopologyMsg (with a RS)
+
+ // wait and process Topo from remote RS
+ inTopoMsg = waitAndProcessTopoFromRemoteRS();
+
+ // send our own TopologyMsg to remote RS
+ TopologyMsg outTopoMsg = sendTopoToRemoteRS();
+
+ // log
+ logTopoHandshakeRCVandSND(inTopoMsg, outTopoMsg);
+ }
+ else
+ {
+ // Terminate connection from a V1 RS
+
+ // if the remote RS and the local RS have the same genID
+ // then it's ok and nothing else to do
+ if (generationId == localGenerationId)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() +
+ this + " RS V1 with serverID=" + serverId +
+ " is connected with the right generation ID");
+ }
+ } else
+ {
+ if (localGenerationId > 0)
+ {
+ // if the local RS is initialized
+ if (generationId > 0)
+ {
+ // if the remote RS is initialized
+ if (generationId != localGenerationId)
+ {
+ // if the 2 RS have different generationID
+ if (replicationServerDomain.getGenerationIdSavedStatus())
+ {
+ // if the present RS has received changes regarding its
+ // gen ID and so won't change without a reset
+ // then we are just degrading the peer.
+ Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(localGenerationId));
+ logError(message);
+ } else
+ {
+ // The present RS has never received changes regarding its
+ // gen ID.
+ //
+ // Example case:
+ // - we are in RS1
+ // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
+ // - RS1 has genId1 from LS1 /genId1 comes from data in
+ // suffix
+ // - we are in RS1 and we receive a START msg from RS2
+ // - Each RS keeps its genID / is degraded and when LS2
+ // will be populated from LS1 everything will become ok.
+ //
+ // Issue:
+ // FIXME : Would it be a good idea in some cases to just
+ // set the gen ID received from the peer RS
+ // specially if the peer has a non null state and
+ // we have a nul state ?
+ // replicationServerDomain.
+ // setGenerationId(generationId, false);
+ Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(localGenerationId));
+ logError(message);
+ }
+ }
+ } else
+ {
+ // The remote RS has no genId. We don't change anything for the
+ // current RS.
+ }
+ } else
+ {
+ // The local RS is not initialized - take the one received
+ oldGenerationId =
+ replicationServerDomain.setGenerationId(generationId, false);
+ }
+ }
+
+
+ // Note: the supported scenario for V1->V2 upgrade is to upgrade 1 by 1
+ // all the servers of the topology. We prefer not not send a TopologyMsg
+ // for giving partial/false information to the V2 servers as for
+ // instance we don't have the connected DS of the V1 RS...When the V1
+ // RS will be upgraded in his turn, topo info will be sent and accurate.
+ // That way, there is no risk to have false/incomplete information in
+ // other servers.
+ }
+
+ registerIntoDomain();
+
+ // Process TopologyMsg sent by remote RS: store matching new info
+ // (this will also warn our connected DSs of the new received info)
+ if (inTopoMsg!=null)
+ replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
+
+ super.finalizeStart();
+
+ }
+ catch(DirectoryException de)
+ {
+ abortStart(de.getMessageObject());
+ }
+ catch(Exception e)
+ {
+ abortStart(Message.raw(e.getLocalizedMessage()));
+ }
+ finally
+ {
+ if ((replicationServerDomain != null) &&
+ replicationServerDomain.hasLock())
+ replicationServerDomain.release();
+ }
+ }
+
+ /**
+ * Registers this handler into its related domain and notifies the domain.
+ */
+ private void registerIntoDomain()
+ {
+ // Alright, connected with new RS (either outgoing or incoming
+ // connection): store handler.
+ Map<Short, ReplicationServerHandler> connectedRSs =
+ replicationServerDomain.getConnectedRSs();
+ connectedRSs.put(serverId, this);
+ }
+
+ /**
+ * Create and send the topologyMsg to the remote replication server.
+ * @return the topologyMsg sent.
+ */
+ private TopologyMsg sendTopoToRemoteRS()
+ throws IOException
+ {
+ TopologyMsg outTopoMsg = replicationServerDomain.createTopologyMsgForRS();
+ session.publish(outTopoMsg);
+ return outTopoMsg;
+ }
+
+ /**
+ * Wait receiving the TopologyMsg from the remote RS and process it.
+ * @return the topologyMsg received.
+ * @throws DirectoryException
+ * @throws IOException
+ */
+ private TopologyMsg waitAndProcessTopoFromRemoteRS()
+ throws DirectoryException, IOException
+ {
+ ReplicationMsg msg = null;
+ try
+ {
+ msg = session.receive();
+ }
+ catch(Exception e)
+ {
+ Message message = Message.raw(e.getLocalizedMessage());
+ throw new DirectoryException(ResultCode.OTHER, message);
+ }
+
+ if (!(msg instanceof TopologyMsg))
+ {
+ Message message = ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
+ msg.getClass().getCanonicalName(),
+ "TopologyMsg");
+ abortStart(message);
+ }
+
+ // Remote RS sent his topo msg
+ TopologyMsg inTopoMsg = (TopologyMsg) msg;
+
+ // CONNECTION WITH A RS
+
+ // if the remote RS and the local RS have the same genID
+ // then it's ok and nothing else to do
+ if (generationId == localGenerationId)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + " RS with serverID=" + serverId +
+ " is connected with the right generation ID, same as local ="
+ + generationId);
+ }
+ }
+ else
+ {
+ if (localGenerationId > 0)
+ {
+ // if the local RS is initialized
+ if (generationId > 0)
+ {
+ // if the remote RS is initialized
+ if (generationId != localGenerationId)
+ {
+ // if the 2 RS have different generationID
+ if (replicationServerDomain.getGenerationIdSavedStatus())
+ {
+ // if the present RS has received changes regarding its
+ // gen ID and so won't change without a reset
+ // then we are just degrading the peer.
+ Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(localGenerationId));
+ logError(message);
+ }
+ else
+ {
+ // The present RS has never received changes regarding its
+ // gen ID.
+ //
+ // Example case:
+ // - we are in RS1
+ // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
+ // - RS1 has genId1 from LS1 /genId1 comes from data in
+ // suffix
+ // - we are in RS1 and we receive a START msg from RS2
+ // - Each RS keeps its genID / is degraded and when LS2
+ // will be populated from LS1 everything will become ok.
+ //
+ // Issue:
+ // FIXME : Would it be a good idea in some cases to just
+ // set the gen ID received from the peer RS
+ // specially if the peer has a non null state and
+ // we have a nul state ?
+ // replicationServerDomain.
+ // setGenerationId(generationId, false);
+ Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Long.toString(generationId),
+ Long.toString(localGenerationId));
+ logError(message);
+ }
+ }
+ }
+ else
+ {
+ // The remote RS has no genId. We don't change anything for the
+ // current RS.
+ }
+ }
+ else
+ {
+ // The local RS is not initialized - take the one received
+ // WARNING: Must be done before computing topo message to send
+ // to peer server as topo message must embed valid generation id
+ // for our server
+ oldGenerationId =
+ replicationServerDomain.setGenerationId(generationId, false);
+ }
+ }
+
+ return inTopoMsg;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isDataServer()
+ {
+ return false;
+ }
+
+ /**
+ * Add the DSinfos of the connected Directory Servers
+ * to the List of DSInfo provided as a parameter.
+ *
+ * @param dsInfos The List of DSInfo that should be updated
+ * with the DSInfo for the remoteDirectoryServers
+ * connected to this ServerHandler.
+ */
+ public void addDSInfos(List<DSInfo> dsInfos)
+ {
+ synchronized (remoteDirectoryServers)
+ {
+ for (LightweightServerHandler ls : remoteDirectoryServers.values())
+ {
+ dsInfos.add(ls.toDSInfo());
+ }
+ }
+ }
+
+ /**
+ * Shutdown This ServerHandler.
+ */
+ public void shutdown()
+ {
+ super.shutdown();
+ /*
+ * Stop the remote LSHandler
+ */
+ synchronized (remoteDirectoryServers)
+ {
+ for (LightweightServerHandler lsh : remoteDirectoryServers.values())
+ {
+ lsh.stopHandler();
+ }
+ remoteDirectoryServers.clear();
+ }
+ }
+ /**
+ * Stores topology information received from a peer RS and that must be kept
+ * in RS handler.
+ *
+ * @param topoMsg The received topology message
+ */
+ public void receiveTopoInfoFromRS(TopologyMsg topoMsg)
+ {
+ // Store info for remote RS
+ List<RSInfo> rsInfos = topoMsg.getRsList();
+ // List should only contain RS info for sender
+ RSInfo rsInfo = rsInfos.get(0);
+ generationId = rsInfo.getGenerationId();
+ groupId = rsInfo.getGroupId();
+
+ /**
+ * Store info for DSs connected to the peer RS
+ */
+ List<DSInfo> dsInfos = topoMsg.getDsList();
+
+ synchronized (remoteDirectoryServers)
+ {
+ // Removes the existing structures
+ for (LightweightServerHandler lsh : remoteDirectoryServers.values())
+ {
+ lsh.stopHandler();
+ }
+ remoteDirectoryServers.clear();
+
+ // Creates the new structure according to the message received.
+ for (DSInfo dsInfo : dsInfos)
+ {
+ LightweightServerHandler lsh = new LightweightServerHandler(this,
+ serverId, dsInfo.getDsId(), dsInfo.getGenerationId(),
+ dsInfo.getGroupId(), dsInfo.getStatus(), dsInfo.getRefUrls(),
+ dsInfo.isAssured(), dsInfo.getAssuredMode(),
+ dsInfo.getSafeDataLevel());
+ lsh.startHandler();
+ remoteDirectoryServers.put(lsh.getServerId(), lsh);
+ }
+ }
+ }
+
+ /**
+ * When this handler is connected to a replication server, specifies if
+ * a wanted server is connected to this replication server.
+ *
+ * @param wantedServer The server we want to know if it is connected
+ * to the replication server represented by this handler.
+ * @return boolean True is the wanted server is connected to the server
+ * represented by this handler.
+ */
+ public boolean isRemoteLDAPServer(short wantedServer)
+ {
+ synchronized (remoteDirectoryServers)
+ {
+ for (LightweightServerHandler server : remoteDirectoryServers.values())
+ {
+ if (wantedServer == server.getServerId())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * When the handler is connected to a replication server, specifies the
+ * replication server has remote LDAP servers connected to it.
+ *
+ * @return boolean True is the replication server has remote LDAP servers
+ * connected to it.
+ */
+ public boolean hasRemoteLDAPServers()
+ {
+ synchronized (remoteDirectoryServers)
+ {
+ return !remoteDirectoryServers.isEmpty();
+ }
+ }
+
+ /**
+ * Return a Set containing the servers known by this replicationServer.
+ * @return a set containing the servers known by this replicationServer.
+ */
+ public Set<Short> getConnectedDirectoryServerIds()
+ {
+ synchronized (remoteDirectoryServers)
+ {
+ return remoteDirectoryServers.keySet();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getMonitorInstanceName()
+ {
+ String str = serverURL + " " + String.valueOf(serverId);
+
+ return "Connected Replication Server " + str +
+ ",cn=" + replicationServerDomain.getMonitorInstanceName();
+ }
+
+ /**
+ * Retrieves a set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is requested.
+ *
+ * @return A set of attributes containing monitor data that should be
+ * returned to the client if the corresponding monitor entry is
+ * requested.
+ */
+ @Override
+ public ArrayList<Attribute> getMonitorData()
+ {
+ // Get the generic ones
+ ArrayList<Attribute> attributes = super.getMonitorData();
+
+ // Add the specific RS ones
+ attributes.add(Attributes.create("Replication-Server",
+ serverURL));
+
+ try
+ {
+ MonitorData md;
+ md = replicationServerDomain.computeMonitorData();
+
+ // Missing changes
+ long missingChanges = md.getMissingChangesRS(serverId);
+ attributes.add(Attributes.create("missing-changes", String
+ .valueOf(missingChanges)));
+
+ /* get the Server State */
+ AttributeBuilder builder = new AttributeBuilder("server-state");
+ ServerState state = md.getRSStates(serverId);
+ if (state != null)
+ {
+ for (String str : state.toStringSet())
+ {
+ builder.add(str);
+ }
+ attributes.add(builder.toAttribute());
+ }
+ }
+ catch (Exception e)
+ {
+ Message message =
+ ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
+ // We failed retrieving the monitor data.
+ attributes.add(Attributes.create("error", message.toString()));
+ }
+ return attributes;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ String localString;
+ if (serverId != 0)
+ {
+ localString = "Replication Server ";
+ localString += serverId + " " + serverURL + " " + getServiceId();
+ } else
+ localString = "Unknown server";
+ return localString;
+ }
+ /**
+ * Gets the status of the connected DS.
+ * @return The status of the connected DS.
+ */
+ public ServerStatus getStatus()
+ {
+ return ServerStatus.INVALID_STATUS;
+ }
+ /**
+ * Retrieves the Address URL for this server handler.
+ *
+ * @return The Address URL for this server handler,
+ * in the form of an IP address and port separated by a colon.
+ */
+ public String getServerAddressURL()
+ {
+ return serverAddressURL;
+ }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
index f333bd8..b9bc509 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/SafeDataExpectedAcksInfo.java
@@ -85,7 +85,7 @@
* him with assured safe data mode, we double check here that the ack sender
* is a RS to take the ack into account.
*/
- if (ackingServer.isLDAPserver())
+ if (ackingServer.isDataServer())
{
// Sanity check: this should never happen
if (debugEnabled())
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java
index a64ea71..63df0d1 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerHandler.java
@@ -26,181 +26,217 @@
*/
package org.opends.server.replication.server;
-import org.opends.messages.*;
-
-import static org.opends.server.loggers.ErrorLogger.logError;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.replication.common.StatusMachine.*;
-
-import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.messages.ReplicationMessages.*;
-import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import java.io.IOException;
-import java.util.Date;
-import java.util.List;
import java.util.ArrayList;
-import java.util.Map;
import java.util.Random;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opends.messages.Message;
import org.opends.server.admin.std.server.MonitorProviderCfg;
-import org.opends.server.api.MonitorProvider;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
-import org.opends.server.replication.common.DSInfo;
import org.opends.server.replication.common.RSInfo;
-import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.common.ServerStatus;
-import org.opends.server.replication.common.StatusMachine;
-import org.opends.server.replication.common.StatusMachineEvent;
-import org.opends.server.replication.protocol.*;
+import org.opends.server.replication.protocol.AckMsg;
+import org.opends.server.replication.protocol.ErrorMsg;
+import org.opends.server.replication.protocol.HeartbeatThread;
+import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.ProtocolVersion;
+import org.opends.server.replication.protocol.ReplServerStartMsg;
+import org.opends.server.replication.protocol.ResetGenerationIdMsg;
+import org.opends.server.replication.protocol.RoutableMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.replication.protocol.StartMsg;
+import org.opends.server.replication.protocol.StartSessionMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.protocol.WindowMsg;
+import org.opends.server.replication.protocol.WindowProbeMsg;
import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.Attributes;
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
import org.opends.server.util.TimeThread;
/**
- * This class defines a server handler, which handles all interaction with a
- * peer server (RS or DS).
+ * This class defines a server handler :
+ * - that is a MessageHandler (see this class for more details)
+ * - that handles all interaction with a peer server (RS or DS).
*/
-public class ServerHandler extends MonitorProvider<MonitorProviderCfg>
+public abstract class ServerHandler extends MessageHandler
{
-
- /**
- * The tracer object for the debug logger.
- */
- private static final DebugTracer TRACER = getTracer();
/**
* Time during which the server will wait for existing thread to stop
* during the shutdownWriter.
*/
private static final int SHUTDOWN_JOIN_TIMEOUT = 30000;
- /*
- * Properties, filled if remote server is either a DS or a RS
+ /**
+ * Close the session and log the provided error message
+ * Log nothing if message is null.
+ * @param providedSession The provided closing session.
+ * @param providedMsg The provided error message.
+ * @param handler The handler that manages that session.
*/
- private short serverId;
- private ProtocolSession session;
- private final MsgQueue msgQueue = new MsgQueue();
- private MsgQueue lateQueue = new MsgQueue();
- private ReplicationServerDomain replicationServerDomain = null;
- private String serverURL;
+ static protected void closeSession(ProtocolSession providedSession,
+ Message providedMsg, ServerHandler handler)
+ {
+ if (providedMsg != null)
+ {
+ if (debugEnabled())
+ TRACER.debugInfo("In "
+ + ((handler!=null)?handler.toString():"Replication Server")
+ + " closing session with err=" +
+ providedMsg.toString());
+ logError(providedMsg);
+ }
+ try
+ {
+ if (providedSession!=null)
+ providedSession.close();
+ } catch (IOException ee)
+ {
+ // ignore
+ }
+ }
- // Number of update sent to the server
- private int outCount = 0;
- // Number of updates received from the server
- private int inCount = 0;
+ /**
+ * The serverId of the remote server.
+ */
+ protected short serverId;
+ /**
+ * The session opened with the remote server.
+ */
+ protected ProtocolSession session;
- // Number of updates received from the server in assured safe read mode
- private int assuredSrReceivedUpdates = 0;
- // Number of updates received from the server in assured safe read mode that
- // timed out
- private AtomicInteger assuredSrReceivedUpdatesTimeout = new AtomicInteger();
- // Number of updates sent to the server in assured safe read mode
- private int assuredSrSentUpdates = 0;
- // Number of updates sent to the server in assured safe read mode that timed
- // out
- private AtomicInteger assuredSrSentUpdatesTimeout = new AtomicInteger();
- // Number of updates received from the server in assured safe data mode
- private int assuredSdReceivedUpdates = 0;
- // Number of updates received from the server in assured safe data mode that
- // timed out
- private AtomicInteger assuredSdReceivedUpdatesTimeout = new AtomicInteger();
- // Number of updates sent to the server in assured safe data mode
- private int assuredSdSentUpdates = 0;
- // Number of updates sent to the server in assured safe data mode that timed
- // out
- private AtomicInteger assuredSdSentUpdatesTimeout = new AtomicInteger();
+ /**
+ * The serverURL of the remote server.
+ */
+ protected String serverURL;
+ /**
+ * Number of updates received from the server in assured safe read mode.
+ */
+ protected int assuredSrReceivedUpdates = 0;
+ /**
+ * Number of updates received from the server in assured safe read mode that
+ * timed out.
+ */
+ protected AtomicInteger assuredSrReceivedUpdatesTimeout = new AtomicInteger();
+ /**
+ * Number of updates sent to the server in assured safe read mode.
+ */
+ protected int assuredSrSentUpdates = 0;
+ /**
+ * Number of updates sent to the server in assured safe read mode that timed
+ * out.
+ */
+ protected AtomicInteger assuredSrSentUpdatesTimeout = new AtomicInteger();
+ /**
+ // Number of updates received from the server in assured safe data mode.
+ */
+ protected int assuredSdReceivedUpdates = 0;
+ /**
+ * Number of updates received from the server in assured safe data mode that
+ * timed out.
+ */
+ protected AtomicInteger assuredSdReceivedUpdatesTimeout = new AtomicInteger();
+ /**
+ * Number of updates sent to the server in assured safe data mode.
+ */
+ protected int assuredSdSentUpdates = 0;
- private int maxReceiveQueue = 0;
- private int maxSendQueue = 0;
- private int maxReceiveDelay = 0;
- private int maxSendDelay = 0;
- private int maxQueueSize = 5000;
- private int maxQueueBytesSize = maxQueueSize * 100;
- private int restartReceiveQueue;
- private int restartSendQueue;
- private int restartReceiveDelay;
- private int restartSendDelay;
- private boolean serverIsLDAPserver;
- private boolean following = false;
- private ServerState serverState;
- private boolean activeWriter = true;
- private ServerWriter writer = null;
- private String baseDn = null;
+ /**
+ * Number of updates sent to the server in assured safe data mode that timed
+ * out.
+ */
+ protected AtomicInteger assuredSdSentUpdatesTimeout = new AtomicInteger();
+
+ /**
+ * The associated ServerWriter that sends messages to the remote server.
+ */
+ protected ServerReader reader;
+ /**
+ * The associated ServerReader that receives messages from the remote server.
+ */
+ protected ServerWriter writer = null;
+
+ // window
private int rcvWindow;
private int rcvWindowSizeHalf;
+
private int maxRcvWindow;
- private ServerReader reader;
- private Semaphore sendWindow;
- private int sendWindowSize;
- private boolean flowControl = false; // indicate that the server is
- // flow controlled and should
- // be stopped from sending messages.
+ /**
+ * Semaphore that the writer uses to control the flow to the remote server.
+ */
+ protected Semaphore sendWindow;
+ /**
+ * The initial size of the sending window.
+ */
+ int sendWindowSize;
private int saturationCount = 0;
- private short replicationServerId;
- private short protocolVersion = -1;
- private long generationId = -1;
- // Group id of this remote server
- private byte groupId = (byte) -1;
-
- /*
- * Properties filled only if remote server is a DS
- */
-
- // Status of this DS (only used if this server handler represents a DS)
- private ServerStatus status = ServerStatus.INVALID_STATUS;
- // Referrals URLs this DS is exporting
- private List<String> refUrls = new ArrayList<String>();
- // Assured replication enabled on DS or not
- private boolean assuredFlag = false;
- // DS assured mode (relevant if assured replication enabled)
- private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
- // DS safe data level (relevant if assured mode is safe data)
- private byte safeDataLevel = (byte) -1;
-
- /*
- * Properties filled only if remote server is a RS
- */
- private String serverAddressURL;
/**
- * When this Handler is related to a remote replication server
- * this collection will contain as many elements as there are
- * LDAP servers connected to the remote replication server.
+ * The protocol version established with the remote server.
*/
- private final Map<Short, LightweightServerHandler> directoryServers =
- new ConcurrentHashMap<Short, LightweightServerHandler>();
+ protected short protocolVersion = -1;
+ /**
+ * remote generation id.
+ */
+ protected long generationId = -1;
+ /**
+ * The generation id of the hosting RS.
+ */
+ protected long localGenerationId = -1;
+ /**
+ * The generation id before procesing a new start handshake.
+ */
+ protected long oldGenerationId = -1;
+ /**
+ * Group id of this remote server.
+ */
+ protected byte groupId = (byte) -1;
+ /**
+ * The SSL encryption provided by the creator/starter of this handler.
+ */
+ protected boolean initSslEncryption;
+
+ /**
+ * The SSL encryption after the negociation with the peer.
+ */
+ protected boolean sslEncryption;
/**
* The time in milliseconds between heartbeats from the replication
* server. Zero means heartbeats are off.
*/
- private long heartbeatInterval = 0;
+ protected long heartbeatInterval = 0;
+
/**
* The thread that will send heartbeats.
*/
HeartbeatThread heartbeatThread = null;
+
/**
* Set when ServerWriter is stopping.
*/
- private boolean shutdownWriter = false;
+ protected boolean shutdownWriter = false;
+
/**
* Set when ServerHandler is stopping.
*/
private AtomicBoolean shuttingDown = new AtomicBoolean(false);
+
/**
* Creates a new server handler instance with the provided socket.
*
@@ -208,846 +244,142 @@
* communicate with the remote entity.
* @param queueSize The maximum number of update that will be kept
* in memory by this ServerHandler.
+ * @param replicationServerURL The URL of the hosting replication server.
+ * @param replicationServerId The serverId of the hosting replication server.
+ * @param replicationServer The hosting replication server.
+ * @param rcvWindowSize The window size to receive from the remote server.
*/
- public ServerHandler(ProtocolSession session, int queueSize)
+ public ServerHandler(
+ ProtocolSession session,
+ int queueSize,
+ String replicationServerURL,
+ short replicationServerId,
+ ReplicationServer replicationServer,
+ int rcvWindowSize)
{
- super("Server Handler");
+ super(queueSize, replicationServerURL,
+ replicationServerId, replicationServer);
this.session = session;
- this.maxQueueSize = queueSize;
- this.maxQueueBytesSize = queueSize * 100;
this.protocolVersion = ProtocolVersion.getCurrentVersion();
+ this.rcvWindowSizeHalf = rcvWindowSize / 2;
+ this.maxRcvWindow = rcvWindowSize;
+ this.rcvWindow = rcvWindowSize;
}
/**
- * Creates a DSInfo structure representing this remote DS.
- * @return The DSInfo structure representing this remote DS
+ * Abort a start procedure currently establishing.
+ * @param reason The provided reason.
*/
- public DSInfo toDSInfo()
+ protected void abortStart(Message reason)
{
- DSInfo dsInfo = new DSInfo(serverId, replicationServerId, generationId,
- status, assuredFlag, assuredMode, safeDataLevel, groupId, refUrls);
-
- return dsInfo;
- }
-
- /**
- * Creates a RSInfo structure representing this remote RS.
- * @return The RSInfo structure representing this remote RS
- */
- public RSInfo toRSInfo()
- {
- RSInfo rsInfo = new RSInfo(serverId, generationId, groupId);
-
- return rsInfo;
- }
-
- /**
- * Do the handshake with either the DS or RS and then create the reader and
- * writer thread.
- *
- * There are 2 possible handshake sequences: DS<->RS and RS<->RS. Each one are
- * divided into 2 logical consecutive phases (phase 1 and phase 2):
- *
- * DS<->RS (DS (always initiating connection) always sends first message):
- * -------
- *
- * phase 1:
- * DS --- ServerStartMsg ---> RS
- * DS <--- ReplServerStartMsg --- RS
- * phase 2:
- * DS --- StartSessionMsg ---> RS
- * DS <--- TopologyMsg --- RS
- *
- * RS<->RS (RS initiating connection always sends first message):
- * -------
- *
- * phase 1:
- * RS1 --- ReplServerStartMsg ---> RS2
- * RS1 <--- ReplServerStartMsg --- RS2
- * phase 2:
- * RS1 --- TopologyMsg ---> RS2
- * RS1 <--- TopologyMsg --- RS2
- *
- * @param baseDn baseDn of the ServerHandler when this is an outgoing conn.
- * null if this is an incoming connection (listen).
- * @param replicationServerId The identifier of the replicationServer that
- * creates this server handler.
- * @param replicationServerURL The URL of the replicationServer that creates
- * this server handler.
- * @param windowSize the window size that this server handler must use.
- * @param sslEncryption For outgoing connections indicates whether encryption
- * should be used after the exchange of start messages.
- * Ignored for incoming connections.
- * @param replicationServer the ReplicationServer that created this server
- * handler.
- */
- public void start(String baseDn, short replicationServerId,
- String replicationServerURL,
- int windowSize, boolean sslEncryption,
- ReplicationServer replicationServer)
- {
-
- // The handshake phase must be done by blocking any access to structures
- // keeping info on connected servers, so that one can safely check for
- // pre-existence of a server, send a coherent snapshot of known topology
- // to peers, update the local view of the topology...
- //
- // For instance a kind of problem could be that while we connect with a
- // peer RS, a DS is connecting at the same time and we could publish the
- // connected DSs to the peer RS forgetting this last DS in the TopologyMsg.
- //
- // This method and every others that need to read/make changes to the
- // structures holding topology for the domain should:
- // - call ReplicationServerDomain.lock()
- // - read/modify structures
- // - call ReplicationServerDomain.release()
- //
- // More information is provided in comment of ReplicationServerDomain.lock()
-
- // If domain already exists, lock it until handshake is finished otherwise
- // it will be created and locked later in the method
- if (baseDn != null)
+ // We did not recognize the message, close session as what
+ // can happen after is undetermined and we do not want the server to
+ // be disturbed
+ if (session!=null)
{
- ReplicationServerDomain rsd =
- replicationServer.getReplicationServerDomain(baseDn, false);
- if (rsd != null)
+ try
{
- try
- {
- rsd.lock();
- } catch (InterruptedException ex)
- {
- // Thread interrupted, return.
- return;
- }
+ session.publish(
+ new ErrorMsg(
+ replicationServerDomain.getReplicationServer().getServerId(),
+ serverId,
+ reason));
}
+ catch(Exception e)
+ {
+ }
+ closeSession(session, reason, this);
}
- long oldGenerationId = -100;
+ if ((replicationServerDomain != null) &&
+ replicationServerDomain.hasLock())
+ replicationServerDomain.release();
- if (debugEnabled())
- TRACER.debugInfo("In " + replicationServer.getMonitorInstanceName() +
- " starts a new LS or RS " +
- ((baseDn == null) ? "incoming connection" : "outgoing connection"));
-
- this.replicationServerId = replicationServerId;
- rcvWindowSizeHalf = windowSize / 2;
- maxRcvWindow = windowSize;
- rcvWindow = windowSize;
- long localGenerationId = -1;
- ReplServerStartMsg outReplServerStartMsg = null;
-
- /**
- * This boolean prevents from logging a polluting error when connection\
- * aborted from a DS that wanted only to perform handshake phase 1 in order
- * to determine the best suitable RS:
- * 1) -> ServerStartMsg
- * 2) <- ReplServerStartMsg
- * 3) connection closure
- */
- boolean log_error_message = true;
-
- try
+ // If generation id of domain was changed, set it back to old value
+ // We may have changed it as it was -1 and we received a value >0 from
+ // peer server and the last topo message sent may have failed being
+ // sent: in that case retrieve old value of generation id for
+ // replication server domain
+ if (oldGenerationId != -100)
{
- /*
- * PROCEDE WITH FIRST PHASE OF HANDSHAKE:
- * ServerStartMsg then ReplServerStartMsg (with a DS)
- * OR
- * ReplServerStartMsg then ReplServerStartMsg (with a RS)
- */
+ replicationServerDomain.setGenerationId(oldGenerationId, false);
+ }
+ }
- if (baseDn != null) // Outgoing connection
-
+ /**
+ * Check the protocol window and send WindowMsg if necessary.
+ *
+ * @throws IOException when the session becomes unavailable.
+ */
+ public synchronized void checkWindow() throws IOException
+ {
+ if (rcvWindow < rcvWindowSizeHalf)
+ {
+ if (flowControl)
{
- // This is an outgoing connection. Publish our start message.
- this.baseDn = baseDn;
-
- // Get or create the ReplicationServerDomain
- replicationServerDomain =
- replicationServer.getReplicationServerDomain(baseDn, true);
- if (!replicationServerDomain.hasLock())
+ if (replicationServerDomain.restartAfterSaturation(this))
{
- try
- {
- replicationServerDomain.lock();
- } catch (InterruptedException ex)
- {
- // Thread interrupted, return.
- return;
- }
+ flowControl = false;
}
- localGenerationId = replicationServerDomain.getGenerationId();
+ }
+ if (!flowControl)
+ {
+ WindowMsg msg = new WindowMsg(rcvWindowSizeHalf);
+ session.publish(msg);
+ rcvWindow += rcvWindowSizeHalf;
+ }
+ }
+ }
- ServerState localServerState =
- replicationServerDomain.getDbServerState();
- outReplServerStartMsg = new ReplServerStartMsg(replicationServerId,
- replicationServerURL,
- baseDn, windowSize, localServerState,
- protocolVersion, localGenerationId,
- sslEncryption,
- replicationServer.getGroupId(),
- replicationServerDomain.
- getReplicationServer().getDegradedStatusThreshold());
+ /**
+ * Decrement the protocol window, then check if it is necessary
+ * to send a WindowMsg and send it.
+ *
+ * @throws IOException when the session becomes unavailable.
+ */
+ public synchronized void decAndCheckWindow() throws IOException
+ {
+ rcvWindow--;
+ checkWindow();
+ }
- session.publish(outReplServerStartMsg);
+ /**
+ * Set the shut down flag to true and returns the previous value of the flag.
+ * @return The previous value of the shut down flag
+ */
+ public boolean engageShutdown()
+ {
+ // Use thread safe boolean
+ return shuttingDown.getAndSet(true);
+ }
+
+ /**
+ * Finalize the initialization, create reader, writer, heartbeat system
+ * and monitoring system.
+ * @throws DirectoryException When an exception is raised.
+ */
+ protected void finalizeStart()
+ throws DirectoryException
+ {
+ // FIXME:ECL We should refactor so that a SH always have a session
+ if (session != null)
+ {
+ try
+ {
+ // Disable timeout for next communications
+ session.setSoTimeout(0);
+ }
+ catch(Exception e)
+ {
}
- // Wait and process ServerStartMsg or ReplServerStartMsg
- ReplicationMsg msg = session.receive();
- if (msg instanceof ServerStartMsg)
- {
- // The remote server is an LDAP Server.
- ServerStartMsg serverStartMsg = (ServerStartMsg) msg;
-
- generationId = serverStartMsg.getGenerationId();
- protocolVersion = ProtocolVersion.minWithCurrent(
- serverStartMsg.getVersion());
- serverId = serverStartMsg.getServerId();
- serverURL = serverStartMsg.getServerURL();
- this.baseDn = serverStartMsg.getBaseDn();
- this.serverState = serverStartMsg.getServerState();
- this.groupId = serverStartMsg.getGroupId();
-
- maxReceiveDelay = serverStartMsg.getMaxReceiveDelay();
- maxReceiveQueue = serverStartMsg.getMaxReceiveQueue();
- maxSendDelay = serverStartMsg.getMaxSendDelay();
- maxSendQueue = serverStartMsg.getMaxSendQueue();
- heartbeatInterval = serverStartMsg.getHeartbeatInterval();
-
- // The session initiator decides whether to use SSL.
- sslEncryption = serverStartMsg.getSSLEncryption();
-
- if (maxReceiveQueue > 0)
- restartReceiveQueue = (maxReceiveQueue > 1000 ? maxReceiveQueue -
- 200 : maxReceiveQueue * 8 / 10);
- else
- restartReceiveQueue = 0;
-
- if (maxSendQueue > 0)
- restartSendQueue =
- (maxSendQueue > 1000 ? maxSendQueue - 200 : maxSendQueue * 8 /
- 10);
- else
- restartSendQueue = 0;
-
- if (maxReceiveDelay > 0)
- restartReceiveDelay = (maxReceiveDelay > 10 ? maxReceiveDelay - 1
- : maxReceiveDelay);
- else
- restartReceiveDelay = 0;
-
- if (maxSendDelay > 0)
- restartSendDelay =
- (maxSendDelay > 10 ? maxSendDelay - 1 : maxSendDelay);
- else
- restartSendDelay = 0;
-
- if (heartbeatInterval < 0)
- {
- heartbeatInterval = 0;
- }
-
- serverIsLDAPserver = true;
-
- // Get or Create the ReplicationServerDomain
- replicationServerDomain =
- replicationServer.getReplicationServerDomain(this.baseDn, true);
-
- // Hack to be sure that if a server disconnects and reconnect, we
- // let the reader thread see the closure and cleanup any reference
- // to old connection
- replicationServerDomain.waitDisconnection(serverStartMsg.getServerId());
-
- if (!replicationServerDomain.hasLock())
- {
- try
- {
- replicationServerDomain.lock();
- } catch (InterruptedException ex)
- {
- // Thread interrupted, return.
- return;
- }
- }
-
- // Duplicate server ?
- if (!replicationServerDomain.checkForDuplicateDS(this))
- {
- closeSession(null);
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- return;
- }
-
- localGenerationId = replicationServerDomain.getGenerationId();
-
- ServerState localServerState =
- replicationServerDomain.getDbServerState();
- // This an incoming connection. Publish our start message
- ReplServerStartMsg replServerStartMsg =
- new ReplServerStartMsg(replicationServerId, replicationServerURL,
- this.baseDn, windowSize, localServerState,
- protocolVersion, localGenerationId,
- sslEncryption,
- replicationServer.getGroupId(),
- replicationServerDomain.
- getReplicationServer().getDegradedStatusThreshold());
- session.publish(replServerStartMsg);
- sendWindowSize = serverStartMsg.getWindowSize();
-
- /* Until here session is encrypted then it depends on the
- negotiation */
- if (!sslEncryption)
- {
- session.stopEncryption();
- }
-
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + ":" +
- "\nSH HANDSHAKE RECEIVED:\n" + serverStartMsg.toString() +
- "\nAND REPLIED:\n" + replServerStartMsg.toString());
- }
- } else if (msg instanceof ReplServerStartMsg)
- {
- // The remote server is a replication server
- ReplServerStartMsg inReplServerStartMsg = (ReplServerStartMsg) msg;
- protocolVersion = ProtocolVersion.minWithCurrent(
- inReplServerStartMsg.getVersion());
- generationId = inReplServerStartMsg.getGenerationId();
- serverId = inReplServerStartMsg.getServerId();
- serverURL = inReplServerStartMsg.getServerURL();
- int separator = serverURL.lastIndexOf(':');
- serverAddressURL =
- session.getRemoteAddress() + ":" + serverURL.substring(separator +
- 1);
- serverIsLDAPserver = false;
- if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
- {
- // We support connection from a V1 RS
- // Only V2 protocol has the group id in repl server start message
- this.groupId = inReplServerStartMsg.getGroupId();
- }
- this.baseDn = inReplServerStartMsg.getBaseDn();
-
- if (baseDn == null) // Reply to incoming RS
-
- {
- // Get or create the ReplicationServerDomain
- replicationServerDomain =
- replicationServer.getReplicationServerDomain(this.baseDn, true);
- if (!replicationServerDomain.hasLock())
- {
- try
- {
- /**
- * Take the lock on the domain.
- * WARNING: Here we try to acquire the lock with a timeout. This
- * is for preventing a deadlock that may happen if there are cross
- * connection attempts (for same domain) from this replication
- * server and from a peer one:
- * Here is the scenario:
- * - RS1 connect thread takes the domain lock and starts
- * connection to RS2
- * - at the same time RS2 connect thread takes his domain lock and
- * start connection to RS2
- * - RS2 listen thread starts processing received
- * ReplServerStartMsg from RS1 and wants to acquire the lock on
- * the domain (here) but cannot as RS2 connect thread already has
- * it
- * - RS1 listen thread starts processing received
- * ReplServerStartMsg from RS2 and wants to acquire the lock on
- * the domain (here) but cannot as RS1 connect thread already has
- * it
- * => Deadlock: 4 threads are locked.
- * So to prevent that in such situation, the listen threads here
- * will both timeout trying to acquire the lock. The random time
- * for the timeout should allow on connection attempt to be
- * aborted whereas the other one should have time to finish in the
- * same time.
- * Warning: the minimum time (3s) should be big enough to allow
- * normal situation connections to terminate. The added random
- * time should represent a big enough range so that the chance to
- * have one listen thread timing out a lot before the peer one is
- * great. When the first listen thread times out, the remote
- * connect thread should release the lock and allow the peer
- * listen thread to take the lock it was waiting for and process
- * the connection attempt.
- */
- Random random = new Random();
- int randomTime = random.nextInt(6); // Random from 0 to 5
- // Wait at least 3 seconds + (0 to 5 seconds)
- long timeout = (long) (3000 + ( randomTime * 1000 ) );
- boolean noTimeout = replicationServerDomain.tryLock(timeout);
- if (!noTimeout)
- {
- // Timeout
- Message message = NOTE_TIMEOUT_WHEN_CROSS_CONNECTION.get(
- this.baseDn,
- Short.toString(serverId),
- Short.toString(replicationServer.getServerId()));
- closeSession(message);
- return;
- }
- } catch (InterruptedException ex)
- {
- // Thread interrupted, return.
- return;
- }
- }
- localGenerationId = replicationServerDomain.getGenerationId();
- ServerState domServerState =
- replicationServerDomain.getDbServerState();
-
- // The session initiator decides whether to use SSL.
- sslEncryption = inReplServerStartMsg.getSSLEncryption();
-
- // Publish our start message
- outReplServerStartMsg = new ReplServerStartMsg(replicationServerId,
- replicationServerURL,
- this.baseDn, windowSize, domServerState,
- protocolVersion,
- localGenerationId,
- sslEncryption,
- replicationServer.getGroupId(),
- replicationServerDomain.
- getReplicationServer().getDegradedStatusThreshold());
-
- if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
- {
- session.publish(outReplServerStartMsg);
- } else {
- // We support connection from a V1 RS, send PDU with V1 form
- session.publish(outReplServerStartMsg,
- ProtocolVersion.REPLICATION_PROTOCOL_V1);
- }
-
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + ":" +
- "\nSH HANDSHAKE RECEIVED:\n" + inReplServerStartMsg.toString() +
- "\nAND REPLIED:\n" + outReplServerStartMsg.toString());
- }
- } else
- {
- // Did the remote RS answer with the DN we provided him ?
- if (!(this.baseDn.equals(baseDn)))
- {
- Message message = ERR_RS_DN_DOES_NOT_MATCH.get(
- this.baseDn.toString(),
- baseDn.toString());
- closeSession(message);
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- return;
- }
-
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + ":" +
- "\nSH HANDSHAKE SENT:\n" + outReplServerStartMsg.toString() +
- "\nAND RECEIVED:\n" + inReplServerStartMsg.toString());
- }
- }
- this.serverState = inReplServerStartMsg.getServerState();
- sendWindowSize = inReplServerStartMsg.getWindowSize();
-
- // Duplicate server ?
- if (!replicationServerDomain.checkForDuplicateRS(this))
- {
- closeSession(null);
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- return;
- }
-
- /* Until here session is encrypted then it depends on the
- negociation */
- if (!sslEncryption)
- {
- session.stopEncryption();
- }
- } else
- {
- // We did not recognize the message, close session as what
- // can happen after is undetermined and we do not want the server to
- // be disturbed
- closeSession(null);
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- return;
- }
-
- if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
- { // Only protocol version above V1 has a phase 2 handshake
-
- /*
- * NOW PROCEDE WITH SECOND PHASE OF HANDSHAKE:
- * TopologyMsg then TopologyMsg (with a RS)
- * OR
- * StartSessionMsg then TopologyMsg (with a DS)
- */
-
- TopologyMsg outTopoMsg = null;
-
- if (baseDn != null) // Outgoing connection to a RS
-
- {
- // Send our own TopologyMsg to remote RS
- outTopoMsg = replicationServerDomain.createTopologyMsgForRS();
- session.publish(outTopoMsg);
- }
-
- // Wait and process TopologyMsg or StartSessionMsg
- log_error_message = false;
- ReplicationMsg msg2 = session.receive();
- log_error_message = true;
- if (msg2 instanceof TopologyMsg)
- {
- // Remote RS sent his topo msg
- TopologyMsg inTopoMsg = (TopologyMsg) msg2;
-
- // CONNECTION WITH A RS
-
- // if the remote RS and the local RS have the same genID
- // then it's ok and nothing else to do
- if (generationId == localGenerationId)
- {
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + " RS with serverID=" + serverId +
- " is connected with the right generation ID");
- }
- } else
- {
- if (localGenerationId > 0)
- {
- // if the local RS is initialized
- if (generationId > 0)
- {
- // if the remote RS is initialized
- if (generationId != localGenerationId)
- {
- // if the 2 RS have different generationID
- if (replicationServerDomain.getGenerationIdSavedStatus())
- {
- // if the present RS has received changes regarding its
- // gen ID and so won't change without a reset
- // then we are just degrading the peer.
- Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
- this.baseDn,
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(localGenerationId));
- logError(message);
- } else
- {
- // The present RS has never received changes regarding its
- // gen ID.
- //
- // Example case:
- // - we are in RS1
- // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
- // - RS1 has genId1 from LS1 /genId1 comes from data in
- // suffix
- // - we are in RS1 and we receive a START msg from RS2
- // - Each RS keeps its genID / is degraded and when LS2
- // will be populated from LS1 everything will become ok.
- //
- // Issue:
- // FIXME : Would it be a good idea in some cases to just
- // set the gen ID received from the peer RS
- // specially if the peer has a non null state and
- // we have a nul state ?
- // replicationServerDomain.
- // setGenerationId(generationId, false);
- Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
- this.baseDn,
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(localGenerationId));
- logError(message);
- }
- }
- } else
- {
- // The remote RS has no genId. We don't change anything for the
- // current RS.
- }
- } else
- {
- // The local RS is not initialized - take the one received
- // WARNING: Must be done before computing topo message to send
- // to peer server as topo message must embed valid generation id
- // for our server
- oldGenerationId =
- replicationServerDomain.setGenerationId(generationId, false);
- }
- }
-
- if (baseDn == null) // Reply to the RS (incoming connection)
-
- {
- // Send our own TopologyMsg to remote RS
- outTopoMsg = replicationServerDomain.createTopologyMsgForRS();
- session.publish(outTopoMsg);
-
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + ":" +
- "\nSH HANDSHAKE RECEIVED:\n" + inTopoMsg.toString() +
- "\nAND REPLIED:\n" + outTopoMsg.toString());
- }
- } else
- {
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + ":" +
- "\nSH HANDSHAKE SENT:\n" + outTopoMsg.toString() +
- "\nAND RECEIVED:\n" + inTopoMsg.toString());
- }
- }
-
- // Alright, connected with new RS (either outgoing or incoming
- // connection): store handler.
- Map<Short, ServerHandler> connectedRSs =
- replicationServerDomain.getConnectedRSs();
- connectedRSs.put(serverId, this);
-
- // Process TopologyMsg sent by remote RS: store matching new info
- // (this will also warn our connected DSs of the new received info)
- replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
-
- } else if (msg2 instanceof StartSessionMsg)
- {
- // CONNECTION WITH A DS
-
- // Process StartSessionMsg sent by remote DS
- StartSessionMsg startSessionMsg = (StartSessionMsg) msg2;
-
- this.status = startSessionMsg.getStatus();
- // Sanity check: is it a valid initial status?
- if (!isValidInitialStatus(this.status))
- {
- Message mesg = ERR_RS_INVALID_INIT_STATUS.get(
- this.status.toString(), this.baseDn.toString(),
- Short.toString(serverId));
- closeSession(mesg);
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- return;
- }
- this.refUrls = startSessionMsg.getReferralsURLs();
- this.assuredFlag = startSessionMsg.isAssured();
- this.assuredMode = startSessionMsg.getAssuredMode();
- this.safeDataLevel = startSessionMsg.getSafeDataLevel();
-
- /*
- * If we have already a generationID set for the domain
- * then
- * if the connecting replica has not the same
- * then it is degraded locally and notified by an error message
- * else
- * we set the generationID from the one received
- * (unsaved yet on disk . will be set with the 1rst change
- * received)
- */
- if (localGenerationId > 0)
- {
- if (generationId != localGenerationId)
- {
- Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
- this.baseDn,
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(localGenerationId));
- logError(message);
- }
- } else
- {
- // We are an empty Replicationserver
- if ((generationId > 0) && (!serverState.isEmpty()))
- {
- // If the LDAP server has already sent changes
- // it is not expected to connect to an empty RS
- Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get(
- this.baseDn,
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(localGenerationId));
- logError(message);
- } else
- {
- // The local RS is not initialized - take the one received
- // WARNING: Must be done before computing topo message to send
- // to peer server as topo message must embed valid generation id
- // for our server
- oldGenerationId =
- replicationServerDomain.setGenerationId(generationId, false);
- }
- }
-
- // Send our own TopologyMsg to DS
- outTopoMsg = replicationServerDomain.createTopologyMsgForDS(
- this.serverId);
- session.publish(outTopoMsg);
-
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + ":" +
- "\nSH HANDSHAKE RECEIVED:\n" + startSessionMsg.toString() +
- "\nAND REPLIED:\n" + outTopoMsg.toString());
- }
-
- // Alright, connected with new DS: store handler.
- Map<Short, ServerHandler> connectedDSs =
- replicationServerDomain.getConnectedDSs();
- connectedDSs.put(serverId, this);
-
- // Tell peer DSs a new DS just connected to us
- // No need to resend topo msg to this just new DS so not null
- // argument
- replicationServerDomain.sendTopoInfoToDSs(this);
- // Tell peer RSs a new DS just connected to us
- replicationServerDomain.sendTopoInfoToRSs();
- } else
- {
- // We did not recognize the message, close session as what
- // can happen after is undetermined and we do not want the server to
- // be disturbed
- closeSession(null);
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- return;
- }
- } else
- {
- // Terminate connection from a V1 RS
-
- // if the remote RS and the local RS have the same genID
- // then it's ok and nothing else to do
- if (generationId == localGenerationId)
- {
- if (debugEnabled())
- {
- TRACER.debugInfo("In " +
- replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() + " RS V1 with serverID=" + serverId +
- " is connected with the right generation ID");
- }
- } else
- {
- if (localGenerationId > 0)
- {
- // if the local RS is initialized
- if (generationId > 0)
- {
- // if the remote RS is initialized
- if (generationId != localGenerationId)
- {
- // if the 2 RS have different generationID
- if (replicationServerDomain.getGenerationIdSavedStatus())
- {
- // if the present RS has received changes regarding its
- // gen ID and so won't change without a reset
- // then we are just degrading the peer.
- Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
- this.baseDn,
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(localGenerationId));
- logError(message);
- } else
- {
- // The present RS has never received changes regarding its
- // gen ID.
- //
- // Example case:
- // - we are in RS1
- // - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
- // - RS1 has genId1 from LS1 /genId1 comes from data in
- // suffix
- // - we are in RS1 and we receive a START msg from RS2
- // - Each RS keeps its genID / is degraded and when LS2
- // will be populated from LS1 everything will become ok.
- //
- // Issue:
- // FIXME : Would it be a good idea in some cases to just
- // set the gen ID received from the peer RS
- // specially if the peer has a non null state and
- // we have a nul state ?
- // replicationServerDomain.
- // setGenerationId(generationId, false);
- Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get(
- this.baseDn,
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(localGenerationId));
- logError(message);
- }
- }
- } else
- {
- // The remote RS has no genId. We don't change anything for the
- // current RS.
- }
- } else
- {
- // The local RS is not initialized - take the one received
- oldGenerationId =
- replicationServerDomain.setGenerationId(generationId, false);
- }
- }
-
- // Alright, connected with new incoming V1 RS: store handler.
- Map<Short, ServerHandler> connectedRSs =
- replicationServerDomain.getConnectedRSs();
- connectedRSs.put(serverId, this);
-
- // Note: the supported scenario for V1->V2 upgrade is to upgrade 1 by 1
- // all the servers of the topology. We prefer not not send a TopologyMsg
- // for giving partial/false information to the V2 servers as for
- // instance we don't have the connected DS of the V1 RS...When the V1
- // RS will be upgraded in his turn, topo info will be sent and accurate.
- // That way, there is no risk to have false/incomplete information in
- // other servers.
- }
-
- /*
- * FINALIZE INITIALIZATION:
- * CREATE READER AND WRITER, HEARTBEAT SYSTEM AND UPDATE MONITORING
- * SYSTEM
- */
-
- // Disable timeout for next communications
- session.setSoTimeout(0);
// sendWindow MUST be created before starting the writer
sendWindow = new Semaphore(sendWindowSize);
writer = new ServerWriter(session, serverId,
- this, replicationServerDomain);
+ this, replicationServerDomain);
reader = new ServerReader(session, serverId,
- this, replicationServerDomain);
+ this, replicationServerDomain);
reader.start();
writer.start();
@@ -1056,440 +388,30 @@
if (heartbeatInterval > 0)
{
heartbeatThread = new HeartbeatThread(
- "Replication Heartbeat to DS " + serverURL + " " + serverId +
- " for " + this.baseDn + " in RS " + replicationServerId,
- session, heartbeatInterval / 3);
+ "Replication Heartbeat to " + this +
+ " in RS " + replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName(),
+ session, heartbeatInterval / 3);
heartbeatThread.start();
}
-
- // Create the status analyzer for the domain if not already started
- if (serverIsLDAPserver)
- {
- if (!replicationServerDomain.isRunningStatusAnalyzer())
- {
- if (debugEnabled())
- TRACER.debugInfo("In " + replicationServerDomain.
- getReplicationServer().
- getMonitorInstanceName() +
- " SH for remote server " + this.getMonitorInstanceName() +
- " is starting status analyzer");
- replicationServerDomain.startStatusAnalyzer();
- }
- }
-
- DirectoryServer.deregisterMonitorProvider(getMonitorInstanceName());
- DirectoryServer.registerMonitorProvider(this);
- } catch (NotSupportedOldVersionPDUException e)
- {
- // We do not need to support DS V1 connection, we just accept RS V1
- // connection:
- // We just trash the message, log the event for debug purpose and close
- // the connection
- if (debugEnabled())
- TRACER.debugInfo("In " + replicationServer.getMonitorInstanceName() + ":"
- + e.getMessage());
- closeSession(null);
- } catch (Exception e)
- {
- // We do not want polluting error log if error is due to normal session
- // aborted after handshake phase one from a DS that is searching for best
- // suitable RS.
- if ( log_error_message || (baseDn != null) )
- {
- // some problem happened, reject the connection
- MessageBuilder mb = new MessageBuilder();
- mb.append(ERR_REPLICATION_SERVER_CONNECTION_ERROR.get(
- this.getMonitorInstanceName()));
- mb.append(": " + stackTraceToSingleLineString(e));
- closeSession(mb.toMessage());
- } else
- {
- closeSession(null);
- }
-
- // If generation id of domain was changed, set it back to old value
- // We may have changed it as it was -1 and we received a value >0 from
- // peer server and the last topo message sent may have failed being
- // sent: in that case retrieve old value of generation id for
- // replication server domain
- if (oldGenerationId != -100)
- {
- replicationServerDomain.setGenerationId(oldGenerationId, false);
- }
}
- // Release domain
- if ((replicationServerDomain != null) &&
- replicationServerDomain.hasLock())
- replicationServerDomain.release();
- }
-
- /*
- * Close the session logging the passed error message
- * Log nothing if message is null.
- */
- private void closeSession(Message msg)
- {
- if (msg != null)
- {
- logError(msg);
- }
- try
- {
- session.close();
- } catch (IOException ee)
- {
- // ignore
- }
+ DirectoryServer.deregisterMonitorProvider(getMonitorInstanceName());
+ DirectoryServer.registerMonitorProvider(this);
}
/**
- * get the Server Id.
+ * Sends a message containing a generationId to a peer server.
+ * The peer is expected to be a replication server.
*
- * @return the ID of the server to which this object is linked
- */
- public short getServerId()
- {
- return serverId;
- }
-
- /**
- * Retrieves the Address URL for this server handler.
+ * @param msg The GenerationIdMessage message to be sent.
+ * @throws IOException When it occurs while sending the message,
*
- * @return The Address URL for this server handler,
- * in the form of an IP address and port separated by a colon.
*/
- public String getServerAddressURL()
+ public void forwardGenerationIdToRS(ResetGenerationIdMsg msg)
+ throws IOException
{
- return serverAddressURL;
- }
-
- /**
- * Retrieves the URL for this server handler.
- *
- * @return The URL for this server handler, in the form of an address and
- * port separated by a colon.
- */
- public String getServerURL()
- {
- return serverURL;
- }
-
- /**
- * Increase the counter of updates sent to the server.
- */
- public void incrementOutCount()
- {
- outCount++;
- }
-
- /**
- * Increase the counter of update received from the server.
- */
- public void incrementInCount()
- {
- inCount++;
- }
-
- /**
- * Get the count of updates received from the server.
- * @return the count of update received from the server.
- */
- public int getInCount()
- {
- return inCount;
- }
-
- /**
- * Get the count of updates sent to this server.
- * @return The count of update sent to this server.
- */
- public int getOutCount()
- {
- return outCount;
- }
-
- /**
- * Get the number of updates received from the server in assured safe read
- * mode.
- * @return The number of updates received from the server in assured safe read
- * mode
- */
- public int getAssuredSrReceivedUpdates()
- {
- return assuredSrReceivedUpdates;
- }
-
- /**
- * Get the number of updates received from the server in assured safe read
- * mode that timed out.
- * @return The number of updates received from the server in assured safe read
- * mode that timed out.
- */
- public AtomicInteger getAssuredSrReceivedUpdatesTimeout()
- {
- return assuredSrReceivedUpdatesTimeout;
- }
-
- /**
- * Get the number of updates sent to the server in assured safe read mode.
- * @return The number of updates sent to the server in assured safe read mode
- */
- public int getAssuredSrSentUpdates()
- {
- return assuredSrSentUpdates;
- }
-
- /**
- * Get the number of updates sent to the server in assured safe read mode that
- * timed out.
- * @return The number of updates sent to the server in assured safe read mode
- * that timed out.
- */
- public AtomicInteger getAssuredSrSentUpdatesTimeout()
- {
- return assuredSrSentUpdatesTimeout;
- }
-
- /**
- * Get the number of updates received from the server in assured safe data
- * mode.
- * @return The number of updates received from the server in assured safe data
- * mode
- */
- public int getAssuredSdReceivedUpdates()
- {
- return assuredSdReceivedUpdates;
- }
-
- /**
- * Get the number of updates received from the server in assured safe data
- * mode that timed out.
- * @return The number of updates received from the server in assured safe data
- * mode that timed out.
- */
- public AtomicInteger getAssuredSdReceivedUpdatesTimeout()
- {
- return assuredSdReceivedUpdatesTimeout;
- }
-
- /**
- * Get the number of updates sent to the server in assured safe data mode.
- * @return The number of updates sent to the server in assured safe data mode
- */
- public int getAssuredSdSentUpdates()
- {
- return assuredSdSentUpdates;
- }
-
- /**
- * Get the number of updates sent to the server in assured safe data mode that
- * timed out.
- * @return The number of updates sent to the server in assured safe data mode
- * that timed out.
- */
- public AtomicInteger getAssuredSdSentUpdatesTimeout()
- {
- return assuredSdSentUpdatesTimeout;
- }
-
- /**
- * Increment the number of updates received from the server in assured safe
- * read mode.
- */
- public void incrementAssuredSrReceivedUpdates()
- {
- assuredSrReceivedUpdates++;
- }
-
- /**
- * Increment the number of updates received from the server in assured safe
- * read mode that timed out.
- */
- public void incrementAssuredSrReceivedUpdatesTimeout()
- {
- assuredSrReceivedUpdatesTimeout.incrementAndGet();
- }
-
- /**
- * Increment the number of updates sent to the server in assured safe read
- * mode.
- */
- public void incrementAssuredSrSentUpdates()
- {
- assuredSrSentUpdates++;
- }
-
- /**
- * Increment the number of updates sent to the server in assured safe read
- * mode that timed out.
- */
- public void incrementAssuredSrSentUpdatesTimeout()
- {
- assuredSrSentUpdatesTimeout.incrementAndGet();
- }
-
- /**
- * Increment the number of updates received from the server in assured safe
- * data mode.
- */
- public void incrementAssuredSdReceivedUpdates()
- {
- assuredSdReceivedUpdates++;
- }
-
- /**
- * Increment the number of updates received from the server in assured safe
- * data mode that timed out.
- */
- public void incrementAssuredSdReceivedUpdatesTimeout()
- {
- assuredSdReceivedUpdatesTimeout.incrementAndGet();
- }
-
- /**
- * Increment the number of updates sent to the server in assured safe data
- * mode.
- */
- public void incrementAssuredSdSentUpdates()
- {
- assuredSdSentUpdates++;
- }
-
- /**
- * Increment the number of updates sent to the server in assured safe data
- * mode that timed out.
- */
- public void incrementAssuredSdSentUpdatesTimeout()
- {
- assuredSdSentUpdatesTimeout.incrementAndGet();
- }
-
- /**
- * Check is this server is saturated (this server has already been
- * sent a bunch of updates and has not processed them so they are staying
- * in the message queue for this server an the size of the queue
- * for this server is above the configured limit.
- *
- * The limit can be defined in number of updates or with a maximum delay
- *
- * @param changeNumber The changenumber to use to make the delay calculations.
- * @param sourceHandler The ServerHandler which is sending the update.
- * @return true is saturated false if not saturated.
- */
- public boolean isSaturated(ChangeNumber changeNumber,
- ServerHandler sourceHandler)
- {
- synchronized (msgQueue)
- {
- int size = msgQueue.count();
-
- if ((maxReceiveQueue > 0) && (size >= maxReceiveQueue))
- return true;
-
- if ((sourceHandler.maxSendQueue > 0) &&
- (size >= sourceHandler.maxSendQueue))
- return true;
-
- if (!msgQueue.isEmpty())
- {
- UpdateMsg firstUpdate = msgQueue.first();
-
- if (firstUpdate != null)
- {
- long timeDiff = changeNumber.getTimeSec() -
- firstUpdate.getChangeNumber().getTimeSec();
-
- if ((maxReceiveDelay > 0) && (timeDiff >= maxReceiveDelay))
- return true;
-
- if ((sourceHandler.maxSendDelay > 0) &&
- (timeDiff >= sourceHandler.maxSendDelay))
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Check that the size of the Server Handler messages Queue has lowered
- * below the limit and therefore allowing the reception of messages
- * from other servers to restart.
- * @param source The ServerHandler which was sending the update.
- * can be null.
- * @return true if the processing can restart
- */
- public boolean restartAfterSaturation(ServerHandler source)
- {
- synchronized (msgQueue)
- {
- int queueSize = msgQueue.count();
- if ((maxReceiveQueue > 0) && (queueSize >= restartReceiveQueue))
- return false;
- if ((source != null) && (source.maxSendQueue > 0) &&
- (queueSize >= source.restartSendQueue))
- return false;
-
- if (!msgQueue.isEmpty())
- {
- UpdateMsg firstUpdate = msgQueue.first();
- UpdateMsg lastUpdate = msgQueue.last();
-
- if ((firstUpdate != null) && (lastUpdate != null))
- {
- long timeDiff = lastUpdate.getChangeNumber().getTimeSec() -
- firstUpdate.getChangeNumber().getTimeSec();
- if ((maxReceiveDelay > 0) && (timeDiff >= restartReceiveDelay))
- return false;
- if ((source != null) && (source.maxSendDelay > 0) && (timeDiff >=
- source.restartSendDelay))
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Check if the server associated to this ServerHandler is a replication
- * server.
- * @return true if the server associated to this ServerHandler is a
- * replication server.
- */
- public boolean isReplicationServer()
- {
- return (!serverIsLDAPserver);
- }
-
- /**
- * Get the number of message in the receive message queue.
- * @return Size of the receive message queue.
- */
- public int getRcvMsgQueueSize()
- {
- synchronized (msgQueue)
- {
- /*
- * When the server is up to date or close to be up to date,
- * the number of updates to be sent is the size of the receive queue.
- */
- if (isFollowing())
- return msgQueue.count();
- else
- {
- /**
- * When the server is not able to follow, the msgQueue
- * may become too large and therefore won't contain all the
- * changes. Some changes may only be stored in the backing DB
- * of the servers.
- * The total size of the receive queue is calculated by doing
- * the sum of the number of missing changes for every dbHandler.
- */
- ServerState dbState = replicationServerDomain.getDbServerState();
- return ServerState.diffChanges(dbState, serverState);
- }
- }
+ session.publish(msg);
}
/**
@@ -1536,478 +458,133 @@
}
/**
- * Get the older update time for that server.
- * @return The older update time.
+ * Get the number of updates received from the server in assured safe data
+ * mode.
+ * @return The number of updates received from the server in assured safe data
+ * mode
*/
- public long getOlderUpdateTime()
+ public int getAssuredSdReceivedUpdates()
{
- ChangeNumber olderUpdateCN = getOlderUpdateCN();
- if (olderUpdateCN == null)
- return 0;
- return olderUpdateCN.getTime();
+ return assuredSdReceivedUpdates;
}
/**
- * Get the older Change Number for that server.
- * Returns null when the queue is empty.
- * @return The older change number.
+ * Get the number of updates received from the server in assured safe data
+ * mode that timed out.
+ * @return The number of updates received from the server in assured safe data
+ * mode that timed out.
*/
- public ChangeNumber getOlderUpdateCN()
+ public AtomicInteger getAssuredSdReceivedUpdatesTimeout()
{
- ChangeNumber result = null;
- synchronized (msgQueue)
- {
- if (isFollowing())
- {
- if (msgQueue.isEmpty())
- {
- result = null;
- } else
- {
- UpdateMsg msg = msgQueue.first();
- result = msg.getChangeNumber();
- }
- } else
- {
- if (lateQueue.isEmpty())
- {
- // isFollowing is false AND lateQueue is empty
- // We may be at the very moment when the writer has emptyed the
- // lateQueue when it sent the last update. The writer will fill again
- // the lateQueue when it will send the next update but we are not yet
- // there. So let's take the last change not sent directly from
- // the db.
-
- ReplicationIteratorComparator comparator =
- new ReplicationIteratorComparator();
- SortedSet<ReplicationIterator> iteratorSortedSet =
- new TreeSet<ReplicationIterator>(comparator);
- try
- {
- // Build a list of candidates iterator (i.e. db i.e. server)
- for (short serverId : replicationServerDomain.getServers())
- {
- // get the last already sent CN from that server
- ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
- // get an iterator in this server db from that last change
- ReplicationIterator iterator =
- replicationServerDomain.getChangelogIterator(serverId, lastCsn);
- // if that iterator has changes, then it is a candidate
- // it is added in the sorted list at a position given by its
- // current change (see ReplicationIteratorComparator).
- if ((iterator != null) && (iterator.getChange() != null))
- {
- iteratorSortedSet.add(iterator);
- }
- }
- UpdateMsg msg = iteratorSortedSet.first().getChange();
- result = msg.getChangeNumber();
- } catch (Exception e)
- {
- result = null;
- } finally
- {
- for (ReplicationIterator iterator : iteratorSortedSet)
- {
- iterator.releaseCursor();
- }
- }
- } else
- {
- UpdateMsg msg = lateQueue.first();
- result = msg.getChangeNumber();
- }
- }
- }
- return result;
+ return assuredSdReceivedUpdatesTimeout;
}
/**
- * Check if the LDAP server can follow the speed of the other servers.
- * @return true when the server has all the not yet sent changes
- * in its queue.
+ * Get the number of updates sent to the server in assured safe data mode.
+ * @return The number of updates sent to the server in assured safe data mode
*/
- public boolean isFollowing()
+ public int getAssuredSdSentUpdates()
{
- return following;
+ return assuredSdSentUpdates;
}
/**
- * Set the following flag of this server.
- * @param following the value that should be set.
+ * Get the number of updates sent to the server in assured safe data mode that
+ * timed out.
+ * @return The number of updates sent to the server in assured safe data mode
+ * that timed out.
*/
- public void setFollowing(boolean following)
+ public AtomicInteger getAssuredSdSentUpdatesTimeout()
{
- this.following = following;
+ return assuredSdSentUpdatesTimeout;
}
/**
- * Add an update to the list of updates that must be sent to the server
- * managed by this ServerHandler.
+ * Get the number of updates received from the server in assured safe read
+ * mode.
+ * @return The number of updates received from the server in assured safe read
+ * mode
+ */
+ public int getAssuredSrReceivedUpdates()
+ {
+ return assuredSrReceivedUpdates;
+ }
+
+ /**
+ * Get the number of updates received from the server in assured safe read
+ * mode that timed out.
+ * @return The number of updates received from the server in assured safe read
+ * mode that timed out.
+ */
+ public AtomicInteger getAssuredSrReceivedUpdatesTimeout()
+ {
+ return assuredSrReceivedUpdatesTimeout;
+ }
+
+ /**
+ * Get the number of updates sent to the server in assured safe read mode.
+ * @return The number of updates sent to the server in assured safe read mode
+ */
+ public int getAssuredSrSentUpdates()
+ {
+ return assuredSrSentUpdates;
+ }
+
+ /**
+ * Get the number of updates sent to the server in assured safe read mode that
+ * timed out.
+ * @return The number of updates sent to the server in assured safe read mode
+ * that timed out.
+ */
+ public AtomicInteger getAssuredSrSentUpdatesTimeout()
+ {
+ return assuredSrSentUpdatesTimeout;
+ }
+
+ /**
+ * Returns the Replication Server Domain to which belongs this server handler.
*
- * @param update The update that must be added to the list of updates.
- * @param sourceHandler The server that sent the update.
+ * @return The replication server domain.
*/
- public void add(UpdateMsg update, ServerHandler sourceHandler)
+ public ReplicationServerDomain getDomain()
{
- synchronized (msgQueue)
- {
- /*
- * If queue was empty the writer thread was probably asleep
- * waiting for some changes, wake it up
- */
- if (msgQueue.isEmpty())
- msgQueue.notify();
-
- msgQueue.add(update);
-
- /* TODO : size should be configurable
- * and larger than max-receive-queue-size
- */
- while ((msgQueue.count() > maxQueueSize) ||
- (msgQueue.bytesCount() > maxQueueBytesSize))
- {
- setFollowing(false);
- msgQueue.removeFirst();
- }
- }
-
- if (isSaturated(update.getChangeNumber(), sourceHandler))
- {
- sourceHandler.setSaturated(true);
- }
-
- }
-
- private void setSaturated(boolean value)
- {
- flowControl = value;
+ return this.replicationServerDomain;
}
/**
- * Select the next update that must be sent to the server managed by this
- * ServerHandler.
- *
- * @return the next update that must be sent to the server managed by this
- * ServerHandler.
+ * Returns the value of generationId for that handler.
+ * @return The value of the generationId.
*/
- public UpdateMsg take()
+ public long getGenerationId()
{
- boolean interrupted = true;
- UpdateMsg msg = getnextMessage();
-
- /*
- * When we remove a message from the queue we need to check if another
- * server is waiting in flow control because this queue was too long.
- * This check might cause a performance penalty an therefore it
- * is not done for every message removed but only every few messages.
- */
- if (++saturationCount > 10)
- {
- saturationCount = 0;
- try
- {
- replicationServerDomain.checkAllSaturation();
- } catch (IOException e)
- {
- }
- }
- boolean acquired = false;
- do
- {
- try
- {
- acquired = sendWindow.tryAcquire((long) 500, TimeUnit.MILLISECONDS);
- interrupted = false;
- } catch (InterruptedException e)
- {
- // loop until not interrupted
- }
- } while (((interrupted) || (!acquired)) && (!shutdownWriter));
- if (msg != null)
- {
- incrementOutCount();
- if (msg.isAssured())
- {
- if (msg.getAssuredMode() == AssuredMode.SAFE_READ_MODE)
- {
- incrementAssuredSrSentUpdates();
- } else
- {
- if (!isLDAPserver())
- incrementAssuredSdSentUpdates();
- }
- }
- }
- return msg;
+ return generationId;
}
/**
- * Get the next update that must be sent to the server
- * from the message queue or from the database.
- *
- * @return The next update that must be sent to the server.
+ * Gets the group id of the server represented by this object.
+ * @return The group id of the server represented by this object.
*/
- private UpdateMsg getnextMessage()
+ public byte getGroupId()
{
- UpdateMsg msg;
- while (activeWriter == true)
- {
- if (following == false)
- {
- /* this server is late with regard to some other masters
- * in the topology or just joined the topology.
- * In such cases, we can't keep all changes in the queue
- * without saturating the memory, we therefore use
- * a lateQueue that is filled with a few changes from the changelogDB
- * If this server is able to close the gap, it will start using again
- * the regular msgQueue later.
- */
- if (lateQueue.isEmpty())
- {
- /*
- * Start from the server State
- * Loop until the queue high mark or until no more changes
- * for each known LDAP master
- * get the next CSN after this last one :
- * - try to get next from the file
- * - if not found in the file
- * - try to get the next from the queue
- * select the smallest of changes
- * check if it is in the memory tree
- * yes : lock memory tree.
- * check all changes from the list, remove the ones that
- * are already sent
- * unlock memory tree
- * restart as usual
- * load this change on the delayList
- *
- */
- ReplicationIteratorComparator comparator =
- new ReplicationIteratorComparator();
- SortedSet<ReplicationIterator> iteratorSortedSet =
- new TreeSet<ReplicationIterator>(comparator);
- /* fill the lateQueue */
- for (short serverId : replicationServerDomain.getServers())
- {
- ChangeNumber lastCsn = serverState.getMaxChangeNumber(serverId);
- ReplicationIterator iterator =
- replicationServerDomain.getChangelogIterator(serverId, lastCsn);
- if (iterator != null)
- {
- if (iterator.getChange() != null)
- {
- iteratorSortedSet.add(iterator);
- } else
- {
- iterator.releaseCursor();
- }
- }
- }
-
- // The loop below relies on the fact that it is sorted based
- // on the currentChange of each iterator to consider the next
- // change across all servers.
- // Hence it is necessary to remove and eventual add again an iterator
- // when looping in order to keep consistent the order of the
- // iterators (see ReplicationIteratorComparator.
- while (!iteratorSortedSet.isEmpty() &&
- (lateQueue.count()<100) &&
- (lateQueue.bytesCount()<50000) )
- {
- ReplicationIterator iterator = iteratorSortedSet.first();
- iteratorSortedSet.remove(iterator);
- lateQueue.add(iterator.getChange());
- if (iterator.next())
- iteratorSortedSet.add(iterator);
- else
- iterator.releaseCursor();
- }
- for (ReplicationIterator iterator : iteratorSortedSet)
- {
- iterator.releaseCursor();
- }
- /*
- * Check if the first change in the lateQueue is also on the regular
- * queue
- */
- if (lateQueue.isEmpty())
- {
- synchronized (msgQueue)
- {
- if ((msgQueue.count() < maxQueueSize) &&
- (msgQueue.bytesCount() < maxQueueBytesSize))
- {
- setFollowing(true);
- }
- }
- } else
- {
- msg = lateQueue.first();
- synchronized (msgQueue)
- {
- if (msgQueue.contains(msg))
- {
- /* we finally catch up with the regular queue */
- setFollowing(true);
- lateQueue.clear();
- UpdateMsg msg1;
- do
- {
- msg1 = msgQueue.removeFirst();
- } while (!msg.getChangeNumber().equals(msg1.getChangeNumber()));
- this.updateServerState(msg);
- return msg;
- }
- }
- }
- } else
- {
- /* get the next change from the lateQueue */
- msg = lateQueue.removeFirst();
- this.updateServerState(msg);
- return msg;
- }
- }
- synchronized (msgQueue)
- {
- if (following == true)
- {
- try
- {
- while (msgQueue.isEmpty() && (following == true))
- {
- msgQueue.wait(500);
- if (!activeWriter)
- return null;
- }
- } catch (InterruptedException e)
- {
- return null;
- }
- if (following == true)
- {
- msg = msgQueue.removeFirst();
- if (this.updateServerState(msg))
- {
- /*
- * Only push the message if it has not yet been seen
- * by the other server.
- * Otherwise just loop to select the next message.
- */
- return msg;
- }
- }
- }
- }
- /*
- * Need to loop because following flag may have gone to false between
- * the first check at the beginning of this method
- * and the second check just above.
- */
- }
- return null;
+ return groupId;
}
/**
- * Update the serverState with the last message sent.
- *
- * @param msg the last update sent.
- * @return boolean indicating if the update was meaningful.
+ * Get our heartbeat interval.
+ * @return Our heartbeat interval.
*/
- public boolean updateServerState(UpdateMsg msg)
+ public long getHeartbeatInterval()
{
- return serverState.update(msg.getChangeNumber());
+ return heartbeatInterval;
}
/**
- * Get the state of this server.
- *
- * @return ServerState the state for this server..
+ * Get the count of updates received from the server.
+ * @return the count of update received from the server.
*/
- public ServerState getServerState()
+ public int getInCount()
{
- return serverState;
- }
-
- /**
- * Sends an ack message to the server represented by this object.
- *
- * @param ack The ack message to be sent.
- * @throws IOException In case of Exception thrown sending the ack.
- */
- public void sendAck(AckMsg ack) throws IOException
- {
- session.publish(ack);
- }
-
- /**
- * Check type of server handled.
- *
- * @return true if the handled server is an LDAP server.
- * false if the handled server is a replicationServer
- */
- public boolean isLDAPserver()
- {
- return serverIsLDAPserver;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void initializeMonitorProvider(MonitorProviderCfg configuration)
- throws ConfigException, InitializationException
- {
- // Nothing to do for now
- }
-
- /**
- * Retrieves the name of this monitor provider. It should be unique among all
- * monitor providers, including all instances of the same monitor provider.
- *
- * @return The name of this monitor provider.
- */
- @Override
- public String getMonitorInstanceName()
- {
- String str = serverURL + " " + String.valueOf(serverId);
-
- if (serverIsLDAPserver)
- return "Connected Replica " + str +
- ",cn=" + replicationServerDomain.getMonitorInstanceName();
- else
- return "Connected Replication Server " + str +
- ",cn=" + replicationServerDomain.getMonitorInstanceName();
- }
-
- /**
- * Retrieves the length of time in milliseconds that should elapse between
- * calls to the <CODE>updateMonitorData()</CODE> method. A negative or zero
- * return value indicates that the <CODE>updateMonitorData()</CODE> method
- * should not be periodically invoked.
- *
- * @return The length of time in milliseconds that should elapse between
- * calls to the <CODE>updateMonitorData()</CODE> method.
- */
- @Override
- public long getUpdateInterval()
- {
- /* we don't wont to do polling on this monitor */
- return 0;
- }
-
- /**
- * Performs any processing periodic processing that may be desired to update
- * the information associated with this monitor. Note that best-effort
- * attempts will be made to ensure that calls to this method come
- * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
- * be made.
- */
- @Override
- public void updateMonitorData()
- {
- // As long as getUpdateInterval() returns 0, this will never get called
+ return inCount;
}
/**
@@ -2021,101 +598,11 @@
@Override
public ArrayList<Attribute> getMonitorData()
{
- ArrayList<Attribute> attributes = new ArrayList<Attribute>();
- if (serverIsLDAPserver)
- {
- attributes.add(Attributes.create("replica", serverURL));
- attributes.add(Attributes.create("connected-to",
- this.replicationServerDomain.getReplicationServer()
- .getMonitorInstanceName()));
+ // Get the generic ones
+ ArrayList<Attribute> attributes = super.getMonitorData();
- }
- else
- {
- attributes.add(Attributes.create("Replication-Server",
- serverURL));
- }
- attributes.add(Attributes.create("server-id", String
- .valueOf(serverId)));
- attributes.add(Attributes.create("domain-name", baseDn.toString()));
-
- try
- {
- MonitorData md;
- md = replicationServerDomain.computeMonitorData();
-
- if (serverIsLDAPserver)
- {
- // Oldest missing update
- Long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId);
- if ((approxFirstMissingDate != null) && (approxFirstMissingDate > 0))
- {
- Date date = new Date(approxFirstMissingDate);
- attributes.add(Attributes.create(
- "approx-older-change-not-synchronized", date.toString()));
- attributes.add(Attributes.create(
- "approx-older-change-not-synchronized-millis", String
- .valueOf(approxFirstMissingDate)));
- }
-
- // Missing changes
- long missingChanges = md.getMissingChanges(serverId);
- attributes.add(Attributes.create("missing-changes", String
- .valueOf(missingChanges)));
-
- // Replication delay
- long delay = md.getApproxDelay(serverId);
- attributes.add(Attributes.create("approximate-delay", String
- .valueOf(delay)));
-
- /* get the Server State */
- AttributeBuilder builder = new AttributeBuilder("server-state");
- ServerState state = md.getLDAPServerState(serverId);
- if (state != null)
- {
- for (String str : state.toStringSet())
- {
- builder.add(str);
- }
- attributes.add(builder.toAttribute());
- }
- }
- else
- {
- // Missing changes
- long missingChanges = md.getMissingChangesRS(serverId);
- attributes.add(Attributes.create("missing-changes", String
- .valueOf(missingChanges)));
-
- /* get the Server State */
- AttributeBuilder builder = new AttributeBuilder("server-state");
- ServerState state = md.getRSStates(serverId);
- if (state != null)
- {
- for (String str : state.toStringSet())
- {
- builder.add(str);
- }
- attributes.add(builder.toAttribute());
- }
- }
- }
- catch (Exception e)
- {
- Message message =
- ERR_ERROR_RETRIEVING_MONITOR_DATA.get(stackTraceToSingleLineString(e));
- // We failed retrieving the monitor data.
- attributes.add(Attributes.create("error", message.toString()));
- }
-
- attributes.add(
- Attributes.create("queue-size", String.valueOf(msgQueue.count())));
- attributes.add(
- Attributes.create(
- "queue-size-bytes", String.valueOf(msgQueue.bytesCount())));
- attributes.add(
- Attributes.create(
- "following", String.valueOf(following)));
+ attributes.add(Attributes.create("server-id", String.valueOf(serverId)));
+ attributes.add(Attributes.create("domain-name", getServiceId().toString()));
// Deprecated
attributes.add(Attributes.create("max-waiting-changes", String
@@ -2129,23 +616,23 @@
attributes.add(Attributes.create("assured-sr-received-updates", String
.valueOf(getAssuredSrReceivedUpdates())));
attributes.add(Attributes.create("assured-sr-received-updates-timeout",
- String .valueOf(getAssuredSrReceivedUpdatesTimeout())));
+ String .valueOf(getAssuredSrReceivedUpdatesTimeout())));
attributes.add(Attributes.create("assured-sr-sent-updates", String
.valueOf(getAssuredSrSentUpdates())));
attributes.add(Attributes.create("assured-sr-sent-updates-timeout", String
.valueOf(getAssuredSrSentUpdatesTimeout())));
attributes.add(Attributes.create("assured-sd-received-updates", String
.valueOf(getAssuredSdReceivedUpdates())));
- if (!isLDAPserver())
+ if (!isDataServer())
{
attributes.add(Attributes.create("assured-sd-sent-updates",
- String.valueOf(getAssuredSdSentUpdates())));
+ String.valueOf(getAssuredSdSentUpdates())));
attributes.add(Attributes.create("assured-sd-sent-updates-timeout",
- String.valueOf(getAssuredSdSentUpdatesTimeout())));
+ String.valueOf(getAssuredSdSentUpdatesTimeout())));
} else
{
attributes.add(Attributes.create("assured-sd-received-updates-timeout",
- String.valueOf(getAssuredSdReceivedUpdatesTimeout())));
+ String.valueOf(getAssuredSdReceivedUpdatesTimeout())));
}
// Window stats
@@ -2170,44 +657,484 @@
}
/**
+ * Retrieves the name of this monitor provider. It should be unique among all
+ * monitor providers, including all instances of the same monitor provider.
+ *
+ * @return The name of this monitor provider.
+ */
+ @Override
+ public abstract String getMonitorInstanceName();
+
+ /**
+ * Get the older update time for that server.
+ * @return The older update time.
+ */
+ public long getOlderUpdateTime()
+ {
+ ChangeNumber olderUpdateCN = getOlderUpdateCN();
+ if (olderUpdateCN == null)
+ return 0;
+ return olderUpdateCN.getTime();
+ }
+
+ /**
+ * Get the count of updates sent to this server.
+ * @return The count of update sent to this server.
+ */
+ public int getOutCount()
+ {
+ return outCount;
+ }
+
+ /**
+ * Gets the protocol version used with this remote server.
+ * @return The protocol version used with this remote server.
+ */
+ public short getProtocolVersion()
+ {
+ return protocolVersion;
+ }
+
+ /**
+ * get the Server Id.
+ *
+ * @return the ID of the server to which this object is linked
+ */
+ public short getServerId()
+ {
+ return serverId;
+ }
+
+ /**
+ * Retrieves the URL for this server handler.
+ *
+ * @return The URL for this server handler, in the form of an address and
+ * port separated by a colon.
+ */
+ public String getServerURL()
+ {
+ return serverURL;
+ }
+
+ /**
+ * Return the ServerStatus.
+ * @return The server status.
+ */
+ protected abstract ServerStatus getStatus();
+
+ /**
+ * Retrieves the length of time in milliseconds that should elapse between
+ * calls to the <CODE>updateMonitorData()</CODE> method. A negative or zero
+ * return value indicates that the <CODE>updateMonitorData()</CODE> method
+ * should not be periodically invoked.
+ *
+ * @return The length of time in milliseconds that should elapse between
+ * calls to the <CODE>updateMonitorData()</CODE> method.
+ */
+ @Override
+ public long getUpdateInterval()
+ {
+ /* we don't wont to do polling on this monitor */
+ return 0;
+ }
+
+ /**
+ * Increment the number of updates received from the server in assured safe
+ * data mode.
+ */
+ public void incrementAssuredSdReceivedUpdates()
+ {
+ assuredSdReceivedUpdates++;
+ }
+
+ /**
+ * Increment the number of updates received from the server in assured safe
+ * data mode that timed out.
+ */
+ public void incrementAssuredSdReceivedUpdatesTimeout()
+ {
+ assuredSdReceivedUpdatesTimeout.incrementAndGet();
+ }
+
+ /**
+ * Increment the number of updates sent to the server in assured safe data
+ * mode.
+ */
+ public void incrementAssuredSdSentUpdates()
+ {
+ assuredSdSentUpdates++;
+ }
+
+ /**
+ * Increment the number of updates sent to the server in assured safe data
+ * mode that timed out.
+ */
+ public void incrementAssuredSdSentUpdatesTimeout()
+ {
+ assuredSdSentUpdatesTimeout.incrementAndGet();
+ }
+
+ /**
+ * Increment the number of updates received from the server in assured safe
+ * read mode.
+ */
+ public void incrementAssuredSrReceivedUpdates()
+ {
+ assuredSrReceivedUpdates++;
+ }
+
+ /**
+ * Increment the number of updates received from the server in assured safe
+ * read mode that timed out.
+ */
+ public void incrementAssuredSrReceivedUpdatesTimeout()
+ {
+ assuredSrReceivedUpdatesTimeout.incrementAndGet();
+ }
+
+ /**
+ * Increment the number of updates sent to the server in assured safe read
+ * mode.
+ */
+ public void incrementAssuredSrSentUpdates()
+ {
+ assuredSrSentUpdates++;
+ }
+
+ /**
+ * Increment the number of updates sent to the server in assured safe read
+ * mode that timed out.
+ */
+ public void incrementAssuredSrSentUpdatesTimeout()
+ {
+ assuredSrSentUpdatesTimeout.incrementAndGet();
+ }
+
+ /**
+ * Increase the counter of update received from the server.
+ */
+ public void incrementInCount()
+ {
+ inCount++;
+ }
+
+ /**
+ * Increase the counter of updates sent to the server.
+ */
+ public void incrementOutCount()
+ {
+ outCount++;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeMonitorProvider(MonitorProviderCfg configuration)
+ throws ConfigException, InitializationException
+ {
+ // Nothing to do for now
+ }
+
+ /**
+ * Check if the server associated to this ServerHandler is a data server
+ * in the topology.
+ * @return true if the server is a data server.
+ */
+ public abstract boolean isDataServer();
+
+ /**
+ * Check if the server associated to this ServerHandler is a replication
+ * server.
+ * @return true if the server is a replication server.
+ */
+ public boolean isReplicationServer()
+ {
+ return (!this.isDataServer());
+ }
+
+ /**
+ * Lock the domain potentially with a timeout.
+ * @param timedout The provided timeout.
+ * @throws DirectoryException When an exception occurs.
+ */
+ protected void lockDomain(boolean timedout)
+ throws DirectoryException
+ {
+ // The handshake phase must be done by blocking any access to structures
+ // keeping info on connected servers, so that one can safely check for
+ // pre-existence of a server, send a coherent snapshot of known topology
+ // to peers, update the local view of the topology...
+ //
+ // For instance a kind of problem could be that while we connect with a
+ // peer RS, a DS is connecting at the same time and we could publish the
+ // connected DSs to the peer RS forgetting this last DS in the TopologyMsg.
+ //
+ // This method and every others that need to read/make changes to the
+ // structures holding topology for the domain should:
+ // - call ReplicationServerDomain.lock()
+ // - read/modify structures
+ // - call ReplicationServerDomain.release()
+ //
+ // More information is provided in comment of ReplicationServerDomain.lock()
+
+ // If domain already exists, lock it until handshake is finished otherwise
+ // it will be created and locked later in the method
+ try
+ {
+ if (!timedout)
+ {
+ // !timedout
+ if (!replicationServerDomain.hasLock())
+ replicationServerDomain.lock();
+ }
+ else
+ {
+ // timedout
+ /**
+ * Take the lock on the domain.
+ * WARNING: Here we try to acquire the lock with a timeout. This
+ * is for preventing a deadlock that may happen if there are cross
+ * connection attempts (for same domain) from this replication
+ * server and from a peer one:
+ * Here is the scenario:
+ * - RS1 connect thread takes the domain lock and starts
+ * connection to RS2
+ * - at the same time RS2 connect thread takes his domain lock and
+ * start connection to RS2
+ * - RS2 listen thread starts processing received
+ * ReplServerStartMsg from RS1 and wants to acquire the lock on
+ * the domain (here) but cannot as RS2 connect thread already has
+ * it
+ * - RS1 listen thread starts processing received
+ * ReplServerStartMsg from RS2 and wants to acquire the lock on
+ * the domain (here) but cannot as RS1 connect thread already has
+ * it
+ * => Deadlock: 4 threads are locked.
+ * So to prevent that in such situation, the listen threads here
+ * will both timeout trying to acquire the lock. The random time
+ * for the timeout should allow on connection attempt to be
+ * aborted whereas the other one should have time to finish in the
+ * same time.
+ * Warning: the minimum time (3s) should be big enough to allow
+ * normal situation connections to terminate. The added random
+ * time should represent a big enough range so that the chance to
+ * have one listen thread timing out a lot before the peer one is
+ * great. When the first listen thread times out, the remote
+ * connect thread should release the lock and allow the peer
+ * listen thread to take the lock it was waiting for and process
+ * the connection attempt.
+ */
+ Random random = new Random();
+ int randomTime = random.nextInt(6); // Random from 0 to 5
+ // Wait at least 3 seconds + (0 to 5 seconds)
+ long timeout = (long) (3000 + ( randomTime * 1000 ) );
+ boolean noTimeout = replicationServerDomain.tryLock(timeout);
+ if (!noTimeout)
+ {
+ // Timeout
+ Message message = NOTE_TIMEOUT_WHEN_CROSS_CONNECTION.get(
+ getServiceId(),
+ Short.toString(serverId),
+ Short.toString(replicationServerId));
+ throw new DirectoryException(ResultCode.OTHER, message);
+ }
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // Thread interrupted
+ Message message = ERR_EXCEPTION_LOCKING_RS_DOMAIN.get(e.getMessage());
+ logError(message);
+ }
+ }
+
+ /**
+ * Processes a routable message.
+ *
+ * @param msg The message to be processed.
+ */
+ public void process(RoutableMsg msg)
+ {
+ if (debugEnabled())
+ TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + this +
+ " processes received msg:\n" + msg);
+ replicationServerDomain.process(msg, this);
+ }
+
+ /**
+ * Process the reception of a WindowProbeMsg message.
+ *
+ * @param windowProbeMsg The message to process.
+ *
+ * @throws IOException When the session becomes unavailable.
+ */
+ public void process(WindowProbeMsg windowProbeMsg) throws IOException
+ {
+ if (rcvWindow > 0)
+ {
+ // The LDAP server believes that its window is closed
+ // while it is not, this means that some problem happened in the
+ // window exchange procedure !
+ // lets update the LDAP server with out current window size and hope
+ // that everything will work better in the futur.
+ // TODO also log an error message.
+ WindowMsg msg = new WindowMsg(rcvWindow);
+ session.publish(msg);
+ } else
+ {
+ // Both the LDAP server and the replication server believes that the
+ // window is closed. Lets check the flowcontrol in case we
+ // can now resume operations and send a windowMessage if necessary.
+ checkWindow();
+ }
+ }
+
+ /**
+ * Send an InitializeRequestMessage to the server connected through this
+ * handler.
+ *
+ * @param msg The message to be processed
+ * @throws IOException when raised by the underlying session
+ */
+ public void send(RoutableMsg msg) throws IOException
+ {
+ if (debugEnabled())
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + this +
+ " publishes message:\n" + msg);
+ session.publish(msg);
+ }
+
+ /**
+ * Sends an ack message to the server represented by this object.
+ *
+ * @param ack The ack message to be sent.
+ * @throws IOException In case of Exception thrown sending the ack.
+ */
+ public void sendAck(AckMsg ack) throws IOException
+ {
+ session.publish(ack);
+ }
+
+ /**
+ * Send an ErrorMsg to the peer.
+ *
+ * @param errorMsg The message to be sent
+ * @throws IOException when raised by the underlying session
+ */
+ public void sendError(ErrorMsg errorMsg) throws IOException
+ {
+ session.publish(errorMsg);
+ }
+
+ /**
+ * Send the ReplServerStartMsg to the remote server (RS or DS).
+ * @param requestedProtocolVersion The provided protocol version.
+ * @return The ReplServerStartMsg sent.
+ * @throws IOException When an exception occurs.
+ */
+ public ReplServerStartMsg sendStartToRemote(short requestedProtocolVersion)
+ throws IOException
+ {
+ this.localGenerationId = replicationServerDomain.getGenerationId();
+ ReplServerStartMsg outReplServerStartMsg
+ = new ReplServerStartMsg(
+ replicationServerId,
+ replicationServerURL,
+ getServiceId(),
+ maxRcvWindow,
+ replicationServerDomain.getDbServerState(),
+ protocolVersion,
+ localGenerationId,
+ sslEncryption,
+ getLocalGroupId(),
+ replicationServerDomain.
+ getReplicationServer().getDegradedStatusThreshold());
+
+ if (requestedProtocolVersion>0)
+ session.publish(outReplServerStartMsg, requestedProtocolVersion);
+ else
+ session.publish(outReplServerStartMsg);
+
+ return outReplServerStartMsg;
+ }
+
+ /**
+ * Sends the provided TopologyMsg to the peer server.
+ *
+ * @param topoMsg The TopologyMsg message to be sent.
+ * @throws IOException When it occurs while sending the message,
+ *
+ */
+ public void sendTopoInfo(TopologyMsg topoMsg)
+ throws IOException
+ {
+ // V1 Rs do not support the TopologyMsg
+ if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
+ {
+ session.publish(topoMsg);
+ }
+ }
+
+ /**
+ * Set a new generation ID.
+ *
+ * @param generationId The new generation ID
+ *
+ */
+ public void setGenerationId(long generationId)
+ {
+ this.generationId = generationId;
+ }
+
+ /**
+ * Sets the replication server domain associated.
+ * @param rsd The provided replication server domain.
+ */
+ protected void setReplicationServerDomain(ReplicationServerDomain rsd)
+ {
+ this.replicationServerDomain = rsd;
+ }
+
+ /**
+ * Sets the window size when used when sending to the remote.
+ * @param size The provided window size.
+ */
+ protected void setSendWindowSize(int size)
+ {
+ this.sendWindowSize = size;
+ }
+
+ /**
+ * Requests to shutdown the writer.
+ */
+ protected void shutdownWriter()
+ {
+ shutdownWriter = true;
+ }
+
+ /**
* Shutdown This ServerHandler.
*/
public void shutdown()
{
- /*
- * Shutdown ServerWriter
- */
- shutdownWriter = true;
- activeWriter = false;
- synchronized (msgQueue)
- {
- /* wake up the writer thread on an empty queue so that it disappear */
- msgQueue.clear();
- msgQueue.notify();
- msgQueue.notifyAll();
- }
+ shutdownWriter();
+ setConsumerActive(false);
+ super.shutdown();
- /*
- * Close session to end ServerReader or ServerWriter
- */
- try
+ if (session != null)
{
- session.close();
- } catch (IOException e)
- {
- // ignore.
- }
-
- /*
- * Stop the remote LSHandler
- */
- synchronized (directoryServers)
- {
- for (LightweightServerHandler lsh : directoryServers.values())
+ // Close session to end ServerReader or ServerWriter
+ try
{
- lsh.stopHandler();
+ session.close();
+ } catch (IOException e)
+ {
+ // ignore.
}
- directoryServers.clear();
}
/*
@@ -2240,68 +1167,93 @@
{
// don't try anymore to join and return.
}
+ if (debugEnabled())
+ TRACER.debugInfo("SH.shutdowned(" + this + ")");
}
/**
- * {@inheritDoc}
- */
- @Override
- public String toString()
- {
- String localString;
- if (serverId != 0)
- {
- if (serverIsLDAPserver)
- localString = "Directory Server ";
- else
- localString = "Replication Server ";
-
-
- localString += serverId + " " + serverURL + " " + baseDn;
- } else
- localString = "Unknown server";
-
- return localString;
- }
-
- /**
- * Decrement the protocol window, then check if it is necessary
- * to send a WindowMsg and send it.
+ * Select the next update that must be sent to the server managed by this
+ * ServerHandler.
*
- * @throws IOException when the session becomes unavailable.
+ * @return the next update that must be sent to the server managed by this
+ * ServerHandler.
*/
- public synchronized void decAndCheckWindow() throws IOException
+ public UpdateMsg take()
{
- rcvWindow--;
- checkWindow();
- }
+ boolean interrupted = true;
+ UpdateMsg msg = getnextMessage(true); // synchronous:block until msg
- /**
- * Check the protocol window and send WindowMsg if necessary.
- *
- * @throws IOException when the session becomes unavailable.
- */
- public synchronized void checkWindow() throws IOException
- {
- if (rcvWindow < rcvWindowSizeHalf)
+ /*
+ * When we remove a message from the queue we need to check if another
+ * server is waiting in flow control because this queue was too long.
+ * This check might cause a performance penalty an therefore it
+ * is not done for every message removed but only every few messages.
+ */
+ if (++saturationCount > 10)
{
- if (flowControl)
+ saturationCount = 0;
+ try
{
- if (replicationServerDomain.restartAfterSaturation(this))
- {
- flowControl = false;
- }
- }
- if (!flowControl)
+ replicationServerDomain.checkAllSaturation();
+ } catch (IOException e)
{
- WindowMsg msg = new WindowMsg(rcvWindowSizeHalf);
- session.publish(msg);
- rcvWindow += rcvWindowSizeHalf;
}
}
+ boolean acquired = false;
+ do
+ {
+ try
+ {
+ acquired = sendWindow.tryAcquire((long) 500, TimeUnit.MILLISECONDS);
+ interrupted = false;
+ } catch (InterruptedException e)
+ {
+ // loop until not interrupted
+ }
+ } while (((interrupted) || (!acquired)) && (!shutdownWriter));
+ if (msg != null)
+ {
+ incrementOutCount();
+
+ if (msg.isAssured())
+ {
+ if (msg.getAssuredMode() == AssuredMode.SAFE_READ_MODE)
+ {
+ incrementAssuredSrSentUpdates();
+ } else
+ {
+ if (!isDataServer())
+ incrementAssuredSdSentUpdates();
+ }
+ }
+ }
+ return msg;
}
/**
+ * Creates a RSInfo structure representing this remote RS.
+ * @return The RSInfo structure representing this remote RS
+ */
+ public RSInfo toRSInfo()
+ {
+ RSInfo rsInfo = new RSInfo(serverId, generationId, groupId);
+
+ return rsInfo;
+ }
+
+ /**
+ * Performs any processing periodic processing that may be desired to update
+ * the information associated with this monitor. Note that best-effort
+ * attempts will be made to ensure that calls to this method come
+ * <CODE>getUpdateInterval()</CODE> milliseconds apart, but no guarantees will
+ * be made.
+ */
+ @Override
+ public void updateMonitorData()
+ {
+ // As long as getUpdateInterval() returns 0, this will never get called
+ }
+ /**
* Update the send window size based on the credit specified in the
* given window message.
*
@@ -2314,510 +1266,122 @@
}
/**
- * Get our heartbeat interval.
- * @return Our heartbeat interval.
+ * Log the messages involved in the start handshake.
+ * @param inStartMsg The message received first.
+ * @param outStartMsg The message sent in response.
*/
- public long getHeartbeatInterval()
- {
- return heartbeatInterval;
- }
-
- /**
- * Processes a routable message.
- *
- * @param msg The message to be processed.
- */
- public void process(RoutableMsg msg)
+ protected void logStartHandshakeRCVandSND(
+ StartMsg inStartMsg,
+ StartMsg outStartMsg)
{
if (debugEnabled())
- TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- " SH for remote server " + this.getMonitorInstanceName() + ":" +
- "\nprocesses received msg:\n" + msg);
- replicationServerDomain.process(msg, this);
- }
-
- /**
- * Sends the provided TopologyMsg to the peer server.
- *
- * @param topoMsg The TopologyMsg message to be sent.
- * @throws IOException When it occurs while sending the message,
- *
- */
- public void sendTopoInfo(TopologyMsg topoMsg)
- throws IOException
- {
- // V1 Rs do not support the TopologyMsg
- if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
{
- if (debugEnabled())
- TRACER.debugInfo("In " + replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- " SH for remote server " + this.getMonitorInstanceName() + ":" +
- "\nsends message:\n" + topoMsg);
-
- session.publish(topoMsg);
- }
- }
-
- /**
- * Stores topology information received from a peer RS and that must be kept
- * in RS handler.
- *
- * @param topoMsg The received topology message
- */
- public void receiveTopoInfoFromRS(TopologyMsg topoMsg)
- {
- // Store info for remote RS
- List<RSInfo> rsInfos = topoMsg.getRsList();
- // List should only contain RS info for sender
- RSInfo rsInfo = rsInfos.get(0);
- generationId = rsInfo.getGenerationId();
- groupId = rsInfo.getGroupId();
-
- /**
- * Store info for DSs connected to the peer RS
- */
- List<DSInfo> dsInfos = topoMsg.getDsList();
-
- synchronized (directoryServers)
- {
- // Removes the existing structures
- for (LightweightServerHandler lsh : directoryServers.values())
- {
- lsh.stopHandler();
- }
- directoryServers.clear();
-
- // Creates the new structure according to the message received.
- for (DSInfo dsInfo : dsInfos)
- {
- LightweightServerHandler lsh = new LightweightServerHandler(this,
- serverId, dsInfo.getDsId(), dsInfo.getGenerationId(),
- dsInfo.getGroupId(), dsInfo.getStatus(), dsInfo.getRefUrls(),
- dsInfo.isAssured(), dsInfo.getAssuredMode(),
- dsInfo.getSafeDataLevel());
- lsh.startHandler();
- directoryServers.put(lsh.getServerId(), lsh);
- }
- }
- }
-
- /**
- * Process message of a remote server changing his status.
- * @param csMsg The message containing the new status
- * @return The new server status of the DS
- */
- public ServerStatus processNewStatus(ChangeStatusMsg csMsg)
- {
-
- // Sanity check
- if (!serverIsLDAPserver)
- {
- Message msg =
- ERR_RECEIVED_CHANGE_STATUS_NOT_FROM_DS.get(baseDn.toString(),
- Short.toString(serverId), csMsg.toString());
- logError(msg);
- return ServerStatus.INVALID_STATUS;
- }
-
- // Get the status the DS just entered
- ServerStatus reqStatus = csMsg.getNewStatus();
- // Translate new status to a state machine event
- StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus);
- if (event == StatusMachineEvent.INVALID_EVENT)
- {
- Message msg = ERR_RS_INVALID_NEW_STATUS.get(reqStatus.toString(),
- baseDn.toString(), Short.toString(serverId));
- logError(msg);
- return ServerStatus.INVALID_STATUS;
- }
-
- // Check state machine allows this new status
- ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
- if (newStatus == ServerStatus.INVALID_STATUS)
- {
- Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
- Short.toString(serverId), status.toString(), event.toString());
- logError(msg);
- return ServerStatus.INVALID_STATUS;
- }
-
- status = newStatus;
-
- return status;
- }
-
- /**
- * Change the status according to the event generated from the status
- * analyzer.
- * @param event The event to be used for new status computation
- * @return The new status of the DS
- * @throws IOException When raised by the underlying session
- */
- public ServerStatus changeStatusFromStatusAnalyzer(StatusMachineEvent event)
- throws IOException
- {
- // Check state machine allows this new status (Sanity check)
- ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
- if (newStatus == ServerStatus.INVALID_STATUS)
- {
- Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
- Short.toString(serverId), status.toString(), event.toString());
- logError(msg);
- // Status analyzer must only change from NORMAL_STATUS to DEGRADED_STATUS
- // and vice versa. We may are being trying to change the status while for
- // instance another status has just been entered: e.g a full update has
- // just been engaged. In that case, just ignore attempt to change the
- // status
- return newStatus;
- }
-
- // Send message requesting to change the DS status
- ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
- ServerStatus.INVALID_STATUS);
-
- if (debugEnabled())
- {
- TRACER.debugInfo(
- "In RS " +
- replicationServerDomain.getReplicationServer().getServerId() +
- " Sending change status from status analyzer to " + getServerId() +
- " for baseDn " + baseDn + ":\n" + csMsg);
- }
-
- session.publish(csMsg);
-
- status = newStatus;
-
- return newStatus;
- }
-
- /**
- * When this handler is connected to a replication server, specifies if
- * a wanted server is connected to this replication server.
- *
- * @param wantedServer The server we want to know if it is connected
- * to the replication server represented by this handler.
- * @return boolean True is the wanted server is connected to the server
- * represented by this handler.
- */
- public boolean isRemoteLDAPServer(short wantedServer)
- {
- synchronized (directoryServers)
- {
- for (LightweightServerHandler server : directoryServers.values())
- {
- if (wantedServer == server.getServerId())
- {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * When the handler is connected to a replication server, specifies the
- * replication server has remote LDAP servers connected to it.
- *
- * @return boolean True is the replication server has remote LDAP servers
- * connected to it.
- */
- public boolean hasRemoteLDAPServers()
- {
- synchronized (directoryServers)
- {
- return !directoryServers.isEmpty();
- }
- }
-
- /**
- * Send an InitializeRequestMessage to the server connected through this
- * handler.
- *
- * @param msg The message to be processed
- * @throws IOException when raised by the underlying session
- */
- public void send(RoutableMsg msg) throws IOException
- {
- if (debugEnabled())
TRACER.debugInfo("In " +
replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- " SH for remote server " + this.getMonitorInstanceName() + ":" +
- "\nsends message:\n" + msg);
- session.publish(msg);
- }
-
- /**
- * Send an ErrorMsg to the peer.
- *
- * @param errorMsg The message to be sent
- * @throws IOException when raised by the underlying session
- */
- public void sendError(ErrorMsg errorMsg) throws IOException
- {
- session.publish(errorMsg);
- }
-
- /**
- * Process the reception of a WindowProbeMsg message.
- *
- * @param windowProbeMsg The message to process.
- *
- * @throws IOException When the session becomes unavailable.
- */
- public void process(WindowProbeMsg windowProbeMsg) throws IOException
- {
- if (rcvWindow > 0)
- {
- // The LDAP server believes that its window is closed
- // while it is not, this means that some problem happened in the
- // window exchange procedure !
- // lets update the LDAP server with out current window size and hope
- // that everything will work better in the futur.
- // TODO also log an error message.
- WindowMsg msg = new WindowMsg(rcvWindow);
- session.publish(msg);
- } else
- {
- // Both the LDAP server and the replication server believes that the
- // window is closed. Lets check the flowcontrol in case we
- // can now resume operations and send a windowMessage if necessary.
- checkWindow();
+ getMonitorInstanceName() + ", " +
+ this.getClass().getSimpleName() + " " + this + ":" +
+ "\nSH START HANDSHAKE RECEIVED:\n" + inStartMsg.toString()+
+ "\nAND REPLIED:\n" + outStartMsg.toString());
}
}
/**
- * Returns the value of generationId for that handler.
- * @return The value of the generationId.
+ * Log the messages involved in the start handshake.
+ * @param outStartMsg The message sent first.
+ * @param inStartMsg The message received in response.
*/
- public long getGenerationId()
+ protected void logStartHandshakeSNDandRCV(
+ StartMsg outStartMsg,
+ StartMsg inStartMsg)
{
- return generationId;
- }
-
- /**
- * Sends a message containing a generationId to a peer server.
- * The peer is expected to be a replication server.
- *
- * @param msg The GenerationIdMessage message to be sent.
- * @throws IOException When it occurs while sending the message,
- *
- */
- public void forwardGenerationIdToRS(ResetGenerationIdMsg msg)
- throws IOException
- {
- session.publish(msg);
- }
-
- /**
- * Set a new generation ID.
- *
- * @param generationId The new generation ID
- *
- */
- public void setGenerationId(long generationId)
- {
- this.generationId = generationId;
- }
-
- /**
- * Returns the Replication Server Domain to which belongs this server handler.
- *
- * @return The replication server domain.
- */
- public ReplicationServerDomain getDomain()
- {
- return this.replicationServerDomain;
- }
-
- /**
- * Return a Set containing the servers known by this replicationServer.
- * @return a set containing the servers known by this replicationServer.
- */
- public Set<Short> getConnectedDirectoryServerIds()
- {
- synchronized (directoryServers)
- {
- return directoryServers.keySet();
- }
- }
-
- /**
- * Order the peer DS server to change his status or close the connection
- * according to the requested new generation id.
- * @param newGenId The new generation id to take into account
- * @throws IOException If IO error occurred.
- */
- public void changeStatusForResetGenId(long newGenId)
- throws IOException
- {
- StatusMachineEvent event = null;
-
- if (newGenId == -1)
- {
- // The generation id is being made invalid, let's put the DS
- // into BAD_GEN_ID_STATUS
- event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
- } else
- {
- if (newGenId == generationId)
- {
- if (status == ServerStatus.BAD_GEN_ID_STATUS)
- {
- // This server has the good new reference generation id.
- // Close connection with him to force his reconnection: DS will
- // reconnect in NORMAL_STATUS or DEGRADED_STATUS.
-
- if (debugEnabled())
- {
- TRACER.debugInfo(
- "In RS " +
- replicationServerDomain.getReplicationServer().getServerId() +
- ". Closing connection to DS " + getServerId() +
- " for baseDn " + baseDn + " to force reconnection as new local" +
- " generation id and remote one match and DS is in bad gen id: " +
- newGenId);
- }
-
- // Connection closure must not be done calling RSD.stopHandler() as it
- // would rewait the RSD lock that we already must have entering this
- // method. This would lead to a reentrant lock which we do not want.
- // So simply close the session, this will make the hang up appear
- // after the reader thread that took the RSD lock realeases it.
- try
- {
- if (session != null)
- session.close();
- } catch (IOException e)
- {
- // ignore
- }
-
- // NOT_CONNECTED_STATUS is the last one in RS session life: handler
- // will soon disappear after this method call...
- status = ServerStatus.NOT_CONNECTED_STATUS;
- return;
- } else
- {
- if (debugEnabled())
- {
- TRACER.debugInfo(
- "In RS " +
- replicationServerDomain.getReplicationServer().getServerId() +
- ". DS " + getServerId() + " for baseDn " + baseDn +
- " has already generation id " + newGenId +
- " so no ChangeStatusMsg sent to him.");
- }
- return;
- }
- } else
- {
- // This server has a bad generation id compared to new reference one,
- // let's put it into BAD_GEN_ID_STATUS
- event = StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT;
- }
- }
-
- if ((event == StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT) &&
- (status == ServerStatus.FULL_UPDATE_STATUS))
- {
- // Prevent useless error message (full update status cannot lead to bad
- // gen status)
- Message message = NOTE_BAD_GEN_ID_IN_FULL_UPDATE.get(
- Short.toString(replicationServerDomain.
- getReplicationServer().getServerId()),
- baseDn.toString(),
- Short.toString(serverId),
- Long.toString(generationId),
- Long.toString(newGenId));
- logError(message);
- return;
- }
-
- ServerStatus newStatus = StatusMachine.computeNewStatus(status, event);
-
- if (newStatus == ServerStatus.INVALID_STATUS)
- {
- Message msg = ERR_RS_CANNOT_CHANGE_STATUS.get(baseDn.toString(),
- Short.toString(serverId), status.toString(), event.toString());
- logError(msg);
- return;
- }
-
- // Send message requesting to change the DS status
- ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus,
- ServerStatus.INVALID_STATUS);
-
if (debugEnabled())
{
- TRACER.debugInfo(
- "In RS " +
- replicationServerDomain.getReplicationServer().getServerId() +
- " Sending change status for reset gen id to " + getServerId() +
- " for baseDn " + baseDn + ":\n" + csMsg);
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + ", " +
+ this.getClass().getSimpleName() + " " + this + ":" +
+ "\nSH START HANDSHAKE SENT("+ this +
+ "):\n" + outStartMsg.toString()+
+ "\nAND RECEIVED:\n" + inStartMsg.toString());
}
-
- session.publish(csMsg);
-
- status = newStatus;
}
/**
- * Set the shut down flag to true and returns the previous value of the flag.
- * @return The previous value of the shut down flag
+ * Log the messages involved in the Topology handshake.
+ * @param inTopoMsg The message received first.
+ * @param outTopoMsg The message sent in response.
*/
- public boolean engageShutdown()
+ protected void logTopoHandshakeRCVandSND(
+ TopologyMsg inTopoMsg,
+ TopologyMsg outTopoMsg)
{
- // Use thread safe boolean
- return shuttingDown.getAndSet(true);
- }
-
- /**
- * Gets the status of the connected DS.
- * @return The status of the connected DS.
- */
- public ServerStatus getStatus()
- {
- return status;
- }
-
- /**
- * Gets the protocol version used with this remote server.
- * @return The protocol version used with this remote server.
- */
- public short getProtocolVersion()
- {
- return protocolVersion;
- }
-
- /**
- * Add the DSinfos of the connected Directory Servers
- * to the List of DSInfo provided as a parameter.
- *
- * @param dsInfos The List of DSInfo that should be updated
- * with the DSInfo for the directoryServers
- * connected to this ServerHandler.
- */
- public void addDSInfos(List<DSInfo> dsInfos)
- {
- synchronized (directoryServers)
+ if (debugEnabled())
{
- for (LightweightServerHandler ls : directoryServers.values())
- {
- dsInfos.add(ls.toDSInfo());
- }
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + ", " +
+ this.getClass().getSimpleName() + " " + this + ":" +
+ "\nSH TOPO HANDSHAKE RECEIVED:\n" + inTopoMsg.toString() +
+ "\nAND REPLIED:\n" + outTopoMsg.toString());
}
}
/**
- * Gets the group id of the server represented by this object.
- * @return The group id of the server represented by this object.
+ * Log the messages involved in the Topology handshake.
+ * @param outTopoMsg The message sent first.
+ * @param inTopoMsg The message received in response.
*/
- public byte getGroupId()
+ protected void logTopoHandshakeSNDandRCV(
+ TopologyMsg outTopoMsg,
+ TopologyMsg inTopoMsg)
{
- return groupId;
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + ", " +
+ this.getClass().getSimpleName() + " " + this + ":" +
+ "\nSH TOPO HANDSHAKE SENT:\n" + outTopoMsg.toString() +
+ "\nAND RECEIVED:\n" + inTopoMsg.toString());
+ }
}
+
+ /**
+ * Log the messages involved in the Topology/StartSession handshake.
+ * @param inStartSessionMsg The message received first.
+ * @param outTopoMsg The message sent in response.
+ */
+ protected void logStartSessionHandshake(
+ StartSessionMsg inStartSessionMsg,
+ TopologyMsg outTopoMsg)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + ", " +
+ this.getClass().getSimpleName() + " " + this + " :" +
+ "\nSH SESSION HANDSHAKE RECEIVED:\n" +
+ "\nAND REPLIED:\n" + outTopoMsg.toString());
+ }
+ }
+
+ /**
+ * Log the messages involved in the Topology/StartSession handshake.
+ * @param inStartECLSessionMsg The message received first.
+ */
+ protected void logStartECLSessionHandshake(
+ StartECLSessionMsg inStartECLSessionMsg)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In " +
+ replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() + ", " +
+ this.getClass().getSimpleName() + " " + this + " :" +
+ "\nSH SESSION HANDSHAKE RECEIVED:\n" +
+ inStartECLSessionMsg.toString());
+ }
+ }
+
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java
index bea8995..c128db2 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerReader.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
+ * Copyright 2006-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.server;
@@ -93,8 +93,9 @@
ServerHandler handler,
ReplicationServerDomain replicationServerDomain)
{
- super("Replication Reader for " + handler.toString() + " in RS " +
- replicationServerDomain.getReplicationServer().getServerId());
+ super("Replication Reader Thread for handler of " +
+ handler.toString() +
+ " in " + replicationServerDomain);
this.session = session;
this.serverId = serverId;
this.handler = handler;
@@ -106,13 +107,10 @@
*/
public void run()
{
+ Message errMessage = null;
if (debugEnabled())
{
- TRACER.debugInfo(
- "In RS " + replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- (handler.isReplicationServer() ? " RS " : " LS") +
- " reader starting for serverId=" + serverId);
+ TRACER.debugInfo(this.getName() + " starting");
}
/*
* wait on input stream
@@ -127,16 +125,11 @@
{
ReplicationMsg msg = session.receive();
- /*
if (debugEnabled())
{
- TRACER.debugInfo(
- "In RS " + replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- (handler.isReplicationServer()?" From RS ":" From LS")+
- " with serverId=" + serverId + " receives " + msg);
+ TRACER.debugInfo(this.getName() + " receives " + msg);
}
- */
+
if (msg instanceof AckMsg)
{
AckMsg ack = (AckMsg) msg;
@@ -146,7 +139,7 @@
{
boolean filtered = false;
/* Ignore updates in some cases */
- if (handler.isLDAPserver())
+ if (handler.isDataServer())
{
/**
* Ignore updates from DS in bad BAD_GENID_STATUS or
@@ -250,12 +243,36 @@
} else if (msg instanceof TopologyMsg)
{
TopologyMsg topoMsg = (TopologyMsg) msg;
- replicationServerDomain.receiveTopoInfoFromRS(topoMsg,
- handler, true);
+ try
+ {
+ ReplicationServerHandler rsh = (ReplicationServerHandler)handler;
+ replicationServerDomain.receiveTopoInfoFromRS(topoMsg,
+ rsh, true);
+ }
+ catch(Exception e)
+ {
+ errMessage =
+ ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
+ "TopologyMsg", "other");
+ logError(errMessage);
+ }
} else if (msg instanceof ChangeStatusMsg)
{
ChangeStatusMsg csMsg = (ChangeStatusMsg) msg;
- replicationServerDomain.processNewStatus(handler, csMsg);
+ try
+ {
+ DataServerHandler dsh = (DataServerHandler)handler;
+ replicationServerDomain.processNewStatus(dsh, csMsg);
+ }
+ catch(Exception e)
+ {
+ errMessage =
+ ERR_RECEIVED_CHANGE_STATUS_NOT_FROM_DS.get(
+ replicationServerDomain.getBaseDn(),
+ Short.toString(handler.getServerId()),
+ csMsg.toString());
+ logError(errMessage);
+ }
} else if (msg instanceof MonitorRequestMsg)
{
MonitorRequestMsg replServerMonitorRequestMsg =
@@ -271,8 +288,8 @@
* The remote server has sent an unknown message,
* close the conenction.
*/
- Message message = NOTE_READER_NULL_MSG.get(handler.toString());
- logError(message);
+ errMessage = NOTE_READER_NULL_MSG.get(handler.toString());
+ logError(errMessage);
return;
}
} catch (NotSupportedOldVersionPDUException e)
@@ -286,7 +303,8 @@
getMonitorInstanceName() + ":" + e.getMessage());
}
}
- } catch (IOException e)
+ }
+ catch (IOException e)
{
/*
* The connection has been broken
@@ -297,13 +315,15 @@
TRACER.debugInfo(
"In RS " + replicationServerDomain.getReplicationServer().
getMonitorInstanceName() +
- " reader IO EXCEPTION for serverID=" + serverId +
+ " reader IO EXCEPTION for serverID=" + serverId + " " +
+ this + " " +
stackTraceToSingleLineString(e) + " " + e.getLocalizedMessage());
- Message message = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+ errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
Short.toString(replicationServerDomain.
getReplicationServer().getServerId()));
- logError(message);
- } catch (ClassNotFoundException e)
+ logError(errMessage);
+ }
+ catch (ClassNotFoundException e)
{
if (debugEnabled())
TRACER.debugInfo(
@@ -315,49 +335,48 @@
* The remote server has sent an unknown message,
* close the connection.
*/
- Message message = ERR_UNKNOWN_MESSAGE.get(handler.toString());
- logError(message);
- } catch (Exception e)
+ errMessage = ERR_UNKNOWN_MESSAGE.get(handler.toString());
+ logError(errMessage);
+ }
+ catch (Exception e)
{
if (debugEnabled())
TRACER.debugInfo(
"In RS <" + replicationServerDomain.getReplicationServer().
getMonitorInstanceName() +
" server reader EXCEPTION serverID=" + serverId +
- stackTraceToSingleLineString(e));
+ " " + stackTraceToSingleLineString(e));
/*
* The remote server has sent an unknown message,
* close the connection.
*/
- Message message = NOTE_READER_EXCEPTION.get(handler.toString());
- logError(message);
- } finally
+ errMessage = NOTE_READER_EXCEPTION.get(handler.toString());
+ logError(errMessage);
+ }
+ finally
{
/*
* The thread only exit the loop above is some error condition
* happen.
* Attempt to close the socket and stop the server handler.
*/
- if (debugEnabled())
- TRACER.debugInfo(
- "In RS " + replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- " server reader for serverID=" + serverId +
- " is closing the session");
try
{
+ if (debugEnabled())
+ TRACER.debugInfo(
+ "In RS " + replicationServerDomain.getReplicationServer().
+ getMonitorInstanceName() +
+ this + " is closing the session");
session.close();
} catch (IOException e)
{
// ignore
}
replicationServerDomain.stopServer(handler);
+ if (debugEnabled())
+ {
+ TRACER.debugInfo(this.getName() + " stopped " + errMessage);
+ }
}
- if (debugEnabled())
- TRACER.debugInfo(
- "In RS " + replicationServerDomain.getReplicationServer().
- getMonitorInstanceName() +
- (handler.isReplicationServer() ? " RS" : " LDAP") +
- " server reader stopped for serverID=" + serverId);
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java
index 90492e8..340983a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/ServerWriter.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
+ * Copyright 2006-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.server;
import org.opends.messages.Message;
@@ -58,7 +58,6 @@
private ProtocolSession session;
private ServerHandler handler;
private ReplicationServerDomain replicationServerDomain;
- private short serverId;
private short protocolVersion = -1;
/**
@@ -76,10 +75,10 @@
ServerHandler handler,
ReplicationServerDomain replicationServerDomain)
{
- super("Replication Writer for " + handler.toString() + " in RS " +
- replicationServerDomain.getReplicationServer().getServerId());
+ super("Replication Writer Thread for handler of " +
+ handler.toString() +
+ " in " + replicationServerDomain);
- this.serverId = serverId;
this.session = session;
this.handler = handler;
this.replicationServerDomain = replicationServerDomain;
@@ -94,16 +93,10 @@
*/
public void run()
{
+ Message errMessage = null;
if (debugEnabled())
{
- if (handler.isReplicationServer())
- {
- TRACER.debugInfo("Replication server writer starting " + serverId);
- }
- else
- {
- TRACER.debugInfo("LDAP server writer starting " + serverId);
- }
+ TRACER.debugInfo(this.getName() + " starting");
}
try
{
@@ -111,10 +104,14 @@
{
UpdateMsg update = replicationServerDomain.take(this.handler);
if (update == null)
+ {
+ errMessage = Message.raw(
+ "Connection closure: null update returned by domain.");
return; /* this connection is closing */
+ }
/* Ignore updates in some cases */
- if (handler.isLDAPserver())
+ if (handler.isDataServer())
{
/**
* Ignore updates to DS in bad BAD_GENID_STATUS or FULL_UPDATE_STATUS
@@ -184,6 +181,7 @@
", writer to " + this.handler.getMonitorInstanceName() +
" publishes msg=[" + update.toString() + "]"+
" refgenId=" + referenceGenerationId +
+ " isAssured=" + update.isAssured() +
" server=" + handler.getServerId() +
" generationId=" + handler.getGenerationId());
}
@@ -200,10 +198,10 @@
* The remote host has disconnected and this particular Tree is going to
* be removed, just ignore the exception and let the thread die as well
*/
- Message message = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+ errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
Short.toString(replicationServerDomain.
getReplicationServer().getServerId()));
- logError(message);
+ logError(errMessage);
}
catch (SocketException e)
{
@@ -211,10 +209,10 @@
* The remote host has disconnected and this particular Tree is going to
* be removed, just ignore the exception and let the thread die as well
*/
- Message message = NOTE_SERVER_DISCONNECT.get(handler.toString(),
+ errMessage = NOTE_SERVER_DISCONNECT.get(handler.toString(),
Short.toString(replicationServerDomain.
getReplicationServer().getServerId()));
- logError(message);
+ logError(errMessage);
}
catch (Exception e)
{
@@ -222,9 +220,9 @@
* An unexpected error happened.
* Log an error and close the connection.
*/
- Message message = ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
+ errMessage = ERR_WRITER_UNEXPECTED_EXCEPTION.get(handler.toString() +
" " + stackTraceToSingleLineString(e));
- logError(message);
+ logError(errMessage);
}
finally {
try
@@ -235,17 +233,9 @@
// Can't do much more : ignore
}
replicationServerDomain.stopServer(handler);
-
if (debugEnabled())
{
- if (handler.isReplicationServer())
- {
- TRACER.debugInfo("Replication server writer stopping " + serverId);
- }
- else
- {
- TRACER.debugInfo("LDAP server writer stopping " + serverId);
- }
+ TRACER.debugInfo(this.getName() + " stopped " + errMessage);
}
}
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java b/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
index 0353276..4b9cd70 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/server/StatusAnalyzer.java
@@ -22,14 +22,13 @@
* CDDL HEADER END
*
*
- * Copyright 2008 Sun Microsystems, Inc.
+ * Copyright 2008-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.server;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
-
import org.opends.server.api.DirectoryThread;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.common.ServerStatus;
@@ -115,7 +114,7 @@
// Go through each connected DS, get the number of pending changes we have
// for it and change status accordingly if threshold value is
// crossed/uncrossed
- for (ServerHandler serverHandler :
+ for (DataServerHandler serverHandler :
replicationServerDomain.getConnectedDSs(). values())
{
// Get number of pending changes for this server
diff --git a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
index c349289..a53ca20 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/replication/service/ReplicationBroker.java
@@ -26,15 +26,9 @@
*/
package org.opends.server.replication.service;
-import org.opends.server.replication.protocol.HeartbeatMonitor;
-
-import org.opends.messages.*;
-
-import static org.opends.server.loggers.ErrorLogger.logError;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-
-import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.messages.ReplicationMessages.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.io.IOException;
@@ -52,13 +46,33 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.Severity;
import org.opends.server.api.DirectoryThread;
+import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.DSInfo;
import org.opends.server.replication.common.RSInfo;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.common.ServerStatus;
-import org.opends.server.replication.protocol.*;
+import org.opends.server.replication.protocol.ChangeStatusMsg;
+import org.opends.server.replication.protocol.HeartbeatMonitor;
+import org.opends.server.replication.protocol.ProtocolSession;
+import org.opends.server.replication.protocol.ProtocolVersion;
+import org.opends.server.replication.protocol.ReplServerStartMsg;
+import org.opends.server.replication.protocol.ReplSessionSecurity;
+import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.ServerStartECLMsg;
+import org.opends.server.replication.protocol.ServerStartMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.replication.protocol.StartSessionMsg;
+import org.opends.server.replication.protocol.TopologyMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.protocol.WindowMsg;
+import org.opends.server.replication.protocol.WindowProbeMsg;
+import org.opends.server.util.ServerConstants;
import org.opends.server.replication.server.ReplicationServer;
/**
@@ -300,6 +314,43 @@
}
}
+ private void connect()
+ {
+ if (this.baseDn.compareToIgnoreCase(
+ ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT)==0)
+ {
+ connectAsECL();
+ }
+ else
+ {
+ connectAsDataServer();
+ }
+ }
+
+ /**
+ * Special aspects of connecting as ECL compared to connecting as data server
+ * are :
+ * - 1 single RS configured
+ * - so no choice of the prefered RS
+ * - No same groupID polling
+ * - ?? Heartbeat
+ * - Start handshake is :
+ * Broker ---> StartECLMsg ---> RS
+ * <---- ReplServerStartMsg ---
+ * ---> StartSessionECLMsg --> RS
+ */
+ private void connectAsECL()
+ {
+ // FIXME:ECL List of RS to connect is for now limited to one RS only
+ String bestServer = this.servers.iterator().next();
+
+ ReplServerStartMsg inReplServerStartMsg
+ = performECLPhaseOneHandshake(bestServer, true);
+
+ if (inReplServerStartMsg!=null)
+ performECLPhaseTwoHandshake(bestServer);
+ }
+
/**
* Connect to a ReplicationServer.
*
@@ -325,7 +376,7 @@
*
* @throws NumberFormatException address was invalid
*/
- private void connect()
+ private void connectAsDataServer()
{
HashMap<String, ServerInfo> rsInfos = new HashMap<String, ServerInfo>();
@@ -350,6 +401,9 @@
* Connect to each replication server and get their ServerState then find
* out which one is the best to connect to.
*/
+ if (debugEnabled())
+ TRACER.debugInfo("phase 1 : will perform PhaseOneH with each RS in " +
+ " order to elect the prefered one");
for (String server : servers)
{
// Connect to server and get reply message
@@ -375,6 +429,9 @@
serverId, baseDn, groupId);
// Best found, now initialize connection to this one (handshake phase 1)
+ if (debugEnabled())
+ TRACER.debugInfo(
+ "phase 2 : will perform PhaseOneH with the prefered RS.");
replServerStartMsg = performPhaseOneHandshake(bestServer, true);
if (replServerStartMsg != null) // Handshake phase 1 exchange went well
@@ -764,6 +821,8 @@
{
try
{
+ if (debugEnabled())
+ TRACER.debugInfo("In RB, closing session after phase 1");
localSession.close();
} catch (IOException e)
{
@@ -789,6 +848,225 @@
}
/**
+ * Connect to the provided server performing the first phase handshake
+ * (start messages exchange) and return the reply message from the replication
+ * server.
+ *
+ * @param server Server to connect to.
+ * @param keepConnection Do we keep session opened or not after handshake.
+ * Use true if want to perform handshake phase 2 with the same session
+ * and keep the session to create as the current one.
+ * @return The ReplServerStartMsg the server replied. Null if could not
+ * get an answer.
+ */
+ private ReplServerStartMsg performECLPhaseOneHandshake(String server,
+ boolean keepConnection)
+ {
+ ReplServerStartMsg replServerStartMsg = null;
+
+ // Parse server string.
+ int separator = server.lastIndexOf(':');
+ String port = server.substring(separator + 1);
+ String hostname = server.substring(0, separator);
+ ProtocolSession localSession = null;
+
+ boolean error = false;
+ try
+ {
+ /*
+ * Open a socket connection to the next candidate.
+ */
+ int intPort = Integer.parseInt(port);
+ InetSocketAddress serverAddr = new InetSocketAddress(
+ InetAddress.getByName(hostname), intPort);
+ if (keepConnection)
+ tmpReadableServerName = serverAddr.toString();
+ Socket socket = new Socket();
+ socket.setReceiveBufferSize(1000000);
+ socket.setTcpNoDelay(true);
+ socket.connect(serverAddr, 500);
+ localSession = replSessionSecurity.createClientSession(server, socket,
+ ReplSessionSecurity.HANDSHAKE_TIMEOUT);
+ boolean isSslEncryption =
+ replSessionSecurity.isSslEncryption(server);
+
+ // Send our start msg.
+ ServerStartECLMsg serverStartECLMsg = new ServerStartECLMsg(
+ baseDn, 0, 0, 0, 0,
+ maxRcvWindow, heartbeatInterval, state,
+ ProtocolVersion.getCurrentVersion(), this.getGenerationID(),
+ isSslEncryption,
+ groupId);
+ localSession.publish(serverStartECLMsg);
+
+ // Read the ReplServerStartMsg that should come back.
+ replServerStartMsg = (ReplServerStartMsg) localSession.receive();
+
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In RB for " + baseDn +
+ "\nRB HANDSHAKE SENT:\n" + serverStartECLMsg.toString() +
+ "\nAND RECEIVED:\n" + replServerStartMsg.toString());
+ }
+
+ // Sanity check
+ String repDn = replServerStartMsg.getBaseDn();
+ if (!(this.baseDn.equals(repDn)))
+ {
+ Message message = ERR_DS_DN_DOES_NOT_MATCH.get(repDn.toString(),
+ this.baseDn);
+ logError(message);
+ error = true;
+ }
+
+ /*
+ * We have sent our own protocol version to the replication server.
+ * The replication server will use the same one (or an older one
+ * if it is an old replication server).
+ */
+ if (keepConnection)
+ protocolVersion = ProtocolVersion.minWithCurrent(
+ replServerStartMsg.getVersion());
+
+ if (!isSslEncryption)
+ {
+ localSession.stopEncryption();
+ }
+ } catch (ConnectException e)
+ {
+ /*
+ * There was no server waiting on this host:port
+ * Log a notice and try the next replicationServer in the list
+ */
+ if (!connectionError)
+ {
+ Message message = NOTE_NO_CHANGELOG_SERVER_LISTENING.get(server);
+ if (keepConnection) // Log error message only for final connection
+ {
+ // the error message is only logged once to avoid overflowing
+ // the error log
+ logError(message);
+ } else if (debugEnabled())
+ {
+ TRACER.debugInfo(message.toString());
+ }
+ }
+ error = true;
+ } catch (Exception e)
+ {
+ if ( (e instanceof SocketTimeoutException) && debugEnabled() )
+ {
+ TRACER.debugInfo("Timeout trying to connect to RS " + server +
+ " for dn: " + baseDn);
+ }
+ Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("1",
+ baseDn, server, e.getLocalizedMessage() +
+ stackTraceToSingleLineString(e));
+ if (keepConnection) // Log error message only for final connection
+ {
+ logError(message);
+ } else if (debugEnabled())
+ {
+ TRACER.debugInfo(message.toString());
+ }
+ error = true;
+ }
+
+ // Close session if requested
+ if (!keepConnection || error)
+ {
+ if (localSession != null)
+ {
+ try
+ {
+ if (debugEnabled())
+ TRACER.debugInfo("In RB, closing session after phase 1");
+ localSession.close();
+ } catch (IOException e)
+ {
+ // The session was already closed, just ignore.
+ }
+ localSession = null;
+ }
+ if (error)
+ {
+ replServerStartMsg = null;
+ } // Be sure to return null.
+
+ }
+
+ // If this connection as the one to use for sending and receiving updates,
+ // store it.
+ if (keepConnection)
+ {
+ session = localSession;
+ }
+
+ return replServerStartMsg;
+ }
+
+ /**
+ * Performs the second phase handshake (send StartSessionMsg and receive
+ * TopologyMsg messages exchange) and return the reply message from the
+ * replication server.
+ *
+ * @param server Server we are connecting with.
+ * @param initStatus The status we are starting with
+ * @return The ReplServerStartMsg the server replied. Null if could not
+ * get an answer.
+ */
+ private TopologyMsg performECLPhaseTwoHandshake(String server)
+ {
+ TopologyMsg topologyMsg = null;
+
+ try
+ {
+ // Send our Start Session
+ StartECLSessionMsg startECLSessionMsg = null;
+ startECLSessionMsg = new StartECLSessionMsg();
+ startECLSessionMsg.setOperationId(Short.toString(serverId));
+ session.publish(startECLSessionMsg);
+
+ /* FIXME:ECL In the handshake phase two, should RS send back a topo msg ?
+ * Read the TopologyMsg that should come back.
+ topologyMsg = (TopologyMsg) session.receive();
+ */
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("In RB for " + baseDn +
+ "\nRB HANDSHAKE SENT:\n" + startECLSessionMsg.toString());
+ // + "\nAND RECEIVED:\n" + topologyMsg.toString());
+ }
+
+ // Alright set the timeout to the desired value
+ session.setSoTimeout(timeout);
+ connected = true;
+
+ } catch (Exception e)
+ {
+ Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("2",
+ baseDn, server, e.getLocalizedMessage() +
+ stackTraceToSingleLineString(e));
+ logError(message);
+
+ if (session != null)
+ {
+ try
+ {
+ session.close();
+ } catch (IOException ex)
+ {
+ // The session was already closed, just ignore.
+ }
+ session = null;
+ }
+ // Be sure to return null.
+ topologyMsg = null;
+ }
+ return topologyMsg;
+ }
+
+ /**
* Performs the second phase handshake (send StartSessionMsg and receive
* TopologyMsg messages exchange) and return the reply message from the
* replication server.
@@ -1720,6 +1998,12 @@
{
boolean done = false;
+ if (debugEnabled())
+ {
+ TRACER.debugInfo("SameGroupIdPoller for: " + baseDn.toString() +
+ " started.");
+ }
+
while ((!done) && (!sameGroupIdPollershutdown))
{
// Sleep some time between checks
@@ -1850,11 +2134,6 @@
*/
public void receiveTopo(TopologyMsg topoMsg)
{
-
- if (debugEnabled())
- TRACER.debugInfo("Replication domain " + baseDn
- + " received topology info update:\n" + topoMsg);
-
// Store new lists
synchronized(getDsList())
{
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index db00003..7230cb6 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -747,6 +747,12 @@
public static final String OC_GROUP_OF_URLS_LC = "groupofurls";
+ /**
+ * The name of the objectclass that will be used as the structural class for
+ * monitor entries.
+ */
+ public static final String OC_CHANGELOG_ENTRY = "changeLogEntry";
+
/**
* The request OID for the cancel extended operation.
@@ -1961,6 +1967,13 @@
"1.3.6.1.4.1.42.2.27.9.5.8";
+ /**
+ * The OID for the entry change request control.
+ * FIXME:ECL ask for OID_ECL_REQUEST_CONTROL
+ */
+ public static final String OID_ECL_COOKIE_EXCHANGE_CONTROL =
+ "1.3.6.1.4.1.26027.1.5.4";
+
/**
* The IANA-assigned OID for the feature allowing a user to request that all
@@ -2983,5 +2996,12 @@
* The normalized false value.
*/
public static final ByteString FALSE_VALUE = ByteString.valueOf("FALSE");
+
+ /**
+ * The root Dn for the external change log.
+ */
+ public static final String DN_EXTERNAL_CHANGELOG_ROOT = "cn=changelog";
+
+
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
new file mode 100644
index 0000000..a711d46
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLSearchOperation.java
@@ -0,0 +1,1300 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2008-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.workflowelement.externalchangelog;
+
+
+
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.needsBase64Encoding;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.Severity;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.plugin.PluginResult;
+import org.opends.server.controls.EntryChangelogNotificationControl;
+import org.opends.server.controls.ExternalChangelogRequestControl;
+import org.opends.server.controls.LDAPAssertionRequestControl;
+import org.opends.server.controls.MatchedValuesControl;
+import org.opends.server.controls.PersistentSearchControl;
+import org.opends.server.controls.ProxiedAuthV1Control;
+import org.opends.server.controls.ProxiedAuthV2Control;
+import org.opends.server.core.AccessControlConfigManager;
+import org.opends.server.core.AddOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.core.PluginConfigManager;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.core.SearchOperationWrapper;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.ExternalChangeLogSession;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.replication.plugin.MultimasterReplication;
+import org.opends.server.replication.protocol.AddMsg;
+import org.opends.server.replication.protocol.DeleteMsg;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.ModifyDNMsg;
+import org.opends.server.replication.protocol.ModifyMsg;
+import org.opends.server.replication.protocol.StartECLSessionMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.server.ReplicationServer;
+import org.opends.server.replication.service.ReplicationDomain;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.CancelRequest;
+import org.opends.server.types.CancelResult;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ObjectClass;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.RawAttribute;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.operation.PostOperationSearchOperation;
+import org.opends.server.types.operation.PreOperationSearchOperation;
+import org.opends.server.types.operation.SearchEntrySearchOperation;
+import org.opends.server.types.operation.SearchReferenceSearchOperation;
+import org.opends.server.util.Base64;
+import org.opends.server.util.ServerConstants;
+import org.opends.server.util.TimeThread;
+
+
+
+/**
+ * This class defines an operation used to search for entries in a local backend
+ * of the Directory Server.
+ */
+public class ECLSearchOperation
+ extends SearchOperationWrapper
+ implements PreOperationSearchOperation, PostOperationSearchOperation,
+ SearchEntrySearchOperation, SearchReferenceSearchOperation
+{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = getTracer();
+
+ /**
+ * The ECL Start Session we'll send to the RS.
+ */
+ private StartECLSessionMsg startECLSessionMsg;
+
+ // The set of objectclasses that will be used in ECL entries.
+ private static HashMap<ObjectClass,String> eclObjectClasses;
+
+ // The associated DN.
+ private DN rootBaseDN;
+
+ // The cookie received in the ECL request control coming along
+ // with the request.
+ MultiDomainServerState requestCookie = null;
+
+ /**
+ * The replication server in which the search on ECL is to be performed.
+ */
+ protected ReplicationServer replicationServer;
+
+ /**
+ * Indicates whether we should actually process the search. This should
+ * only be false if it's a persistent search with changesOnly=true.
+ */
+ protected boolean changesOnly;
+
+ /**
+ * The client connection for the search operation.
+ */
+ protected ClientConnection clientConnection;
+
+ /**
+ * The base DN for the search.
+ */
+ protected DN baseDN;
+
+ /**
+ * The persistent search request, if applicable.
+ */
+ protected PersistentSearch persistentSearch;
+
+ /**
+ * The filter for the search.
+ */
+ protected SearchFilter filter;
+
+ private ExternalChangeLogSession eclSession;
+
+ // The set of supported controls for this WE
+ private HashSet<String> supportedControls;
+
+ // The set of supported features for this WE
+ private HashSet<String> supportedFeatures;
+
+ String privateDomainsBaseDN;
+
+ /**
+ * Creates a new operation that may be used to search for entries in a local
+ * backend of the Directory Server.
+ *
+ * @param search The operation to process.
+ */
+ public ECLSearchOperation(SearchOperation search)
+ {
+ super(search);
+
+ try
+ {
+ rootBaseDN = DN.decode(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT);
+ }
+ catch (Exception e){}
+
+ // Construct the set of objectclasses to include in the base monitor entry.
+ eclObjectClasses = new LinkedHashMap<ObjectClass,String>(2);
+ ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP, true);
+ eclObjectClasses.put(topOC, OC_TOP);
+ ObjectClass eclEntryOC = DirectoryServer.getObjectClass(OC_CHANGELOG_ENTRY,
+ true);
+ eclObjectClasses.put(eclEntryOC, OC_CHANGELOG_ENTRY);
+
+
+ // Define an empty sets for the supported controls and features.
+ supportedControls = new HashSet<String>(0);
+ supportedControls.add(ServerConstants.OID_SERVER_SIDE_SORT_REQUEST_CONTROL);
+ supportedControls.add(ServerConstants.OID_VLV_REQUEST_CONTROL);
+ supportedFeatures = new HashSet<String>(0);
+
+ ECLWorkflowElement.attachLocalOperation(search, this);
+ }
+
+
+
+ /**
+ * Process this search operation against a local backend.
+ *
+ * @param wfe
+ * The local backend work-flow element.
+ * @throws CanceledOperationException
+ * if this operation should be cancelled
+ */
+ public void processECLSearch(ECLWorkflowElement wfe)
+ throws CanceledOperationException
+ {
+ boolean executePostOpPlugins = false;
+ this.replicationServer = wfe.getReplicationServer();
+
+ clientConnection = getClientConnection();
+
+ // Get the plugin config manager that will be used for invoking plugins.
+ PluginConfigManager pluginConfigManager =
+ DirectoryServer.getPluginConfigManager();
+ changesOnly = false;
+
+ // Check for a request to cancel this operation.
+ checkIfCanceled(false);
+
+ // Create a labeled block of code that we can break out of if a problem is
+ // detected.
+searchProcessing:
+ {
+ // Process the search base and filter to convert them from their raw forms
+ // as provided by the client to the forms required for the rest of the
+ // search processing.
+ baseDN = getBaseDN();
+ filter = getFilter();
+
+ if ((baseDN == null) || (filter == null)){
+ break searchProcessing;
+ }
+
+ // Check to see if there are any controls in the request. If so, then
+ // see if there is any special processing required.
+ try
+ {
+ this.requestCookie = null;
+ handleRequestControls();
+ if (this.requestCookie == null)
+ {
+ setResponseData(new DirectoryException(
+ ResultCode.OPERATIONS_ERROR,
+ Message.raw(Category.SYNC, Severity.FATAL_ERROR,
+ "Cookie control expected")));
+ break searchProcessing;
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+ break searchProcessing;
+ }
+
+ // Check for a request to cancel this operation.
+ checkIfCanceled(false);
+
+ // Invoke the pre-operation search plugins.
+ executePostOpPlugins = true;
+ PluginResult.PreOperation preOpResult =
+ pluginConfigManager.invokePreOperationSearchPlugins(this);
+ if (!preOpResult.continueProcessing())
+ {
+ setResultCode(preOpResult.getResultCode());
+ appendErrorMessage(preOpResult.getErrorMessage());
+ setMatchedDN(preOpResult.getMatchedDN());
+ setReferralURLs(preOpResult.getReferralURLs());
+ break searchProcessing;
+ }
+
+
+ // Check for a request to cancel this operation.
+ checkIfCanceled(false);
+
+
+ // Test existence of the RS
+ if (replicationServer == null)
+ {
+ setResultCode(ResultCode.OPERATIONS_ERROR);
+ appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
+ String.valueOf(baseDN)));
+ break searchProcessing;
+ }
+
+
+ // We'll set the result code to "success". If a problem occurs, then it
+ // will be overwritten.
+ setResultCode(ResultCode.SUCCESS);
+
+
+ // If there's a persistent search, then register it with the server.
+ if (persistentSearch != null)
+ {
+ wfe.registerPersistentSearch(persistentSearch);
+ persistentSearch.enable();
+ }
+
+ // Process the search.
+ try
+ {
+ processSearch();
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ setResponseData(de);
+
+ if (persistentSearch != null)
+ {
+ persistentSearch.cancel();
+ setSendResponse(true);
+ }
+
+ break searchProcessing;
+ }
+ catch (CanceledOperationException coe)
+ {
+ if (persistentSearch != null)
+ {
+ persistentSearch.cancel();
+ setSendResponse(true);
+ }
+
+ throw coe;
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ setResultCode(DirectoryServer.getServerErrorResultCode());
+ appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
+ getExceptionMessage(e)));
+
+ if (persistentSearch != null)
+ {
+ persistentSearch.cancel();
+ setSendResponse(true);
+ }
+
+ break searchProcessing;
+ }
+ }
+
+
+ // Check for a request to cancel this operation.
+ checkIfCanceled(false);
+
+ // Invoke the post-operation search plugins.
+ if (executePostOpPlugins)
+ {
+ PluginResult.PostOperation postOpResult =
+ pluginConfigManager.invokePostOperationSearchPlugins(this);
+ if (!postOpResult.continueProcessing())
+ {
+ setResultCode(postOpResult.getResultCode());
+ appendErrorMessage(postOpResult.getErrorMessage());
+ setMatchedDN(postOpResult.getMatchedDN());
+ setReferralURLs(postOpResult.getReferralURLs());
+ }
+ }
+ }
+
+
+ /**
+ * Handles any controls contained in the request.
+ *
+ * @throws DirectoryException If there is a problem with any of the request
+ * controls.
+ */
+ protected void handleRequestControls()
+ throws DirectoryException
+ {
+ List<Control> requestControls = getRequestControls();
+ if ((requestControls != null) && (! requestControls.isEmpty()))
+ {
+ for (int i=0; i < requestControls.size(); i++)
+ {
+ Control c = requestControls.get(i);
+ String oid = c.getOID();
+ if (! AccessControlConfigManager.getInstance().
+ getAccessControlHandler().isAllowed(baseDN, this, c))
+ {
+ throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
+ ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
+ }
+
+ if (oid.equals(OID_ECL_COOKIE_EXCHANGE_CONTROL))
+ {
+ ExternalChangelogRequestControl eclControl =
+ getRequestControl(ExternalChangelogRequestControl.DECODER);
+ this.requestCookie = eclControl.getCookie();
+ }
+ else if (oid.equals(OID_LDAP_ASSERTION))
+ {
+ LDAPAssertionRequestControl assertControl =
+ getRequestControl(LDAPAssertionRequestControl.DECODER);
+
+ try
+ {
+ // FIXME -- We need to determine whether the current user has
+ // permission to make this determination.
+ SearchFilter assertionFilter = assertControl.getSearchFilter();
+ Entry entry;
+ try
+ {
+ entry = DirectoryServer.getEntry(baseDN);
+ }
+ catch (DirectoryException de)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ throw new DirectoryException(de.getResultCode(),
+ ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
+ de.getMessageObject()));
+ }
+
+ if (entry == null)
+ {
+ throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
+ ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
+ }
+
+ if (! assertionFilter.matchesEntry(entry))
+ {
+ throw new DirectoryException(ResultCode.ASSERTION_FAILED,
+ ERR_SEARCH_ASSERTION_FAILED.get());
+ }
+ }
+ catch (DirectoryException de)
+ {
+ if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
+ {
+ throw de;
+ }
+
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+
+ throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+ ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
+ de.getMessageObject()), de);
+ }
+ }
+ else if (oid.equals(OID_PROXIED_AUTH_V1))
+ {
+ // The requester must have the PROXIED_AUTH privilige in order to be
+ // able to use this control.
+ if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+ {
+ throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+ ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+ }
+
+ ProxiedAuthV1Control proxyControl =
+ getRequestControl(ProxiedAuthV1Control.DECODER);
+
+ Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+ setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+ else if (oid.equals(OID_PROXIED_AUTH_V2))
+ {
+ // The requester must have the PROXIED_AUTH privilige in order to be
+ // able to use this control.
+ if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
+ {
+ throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+ ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
+ }
+
+ ProxiedAuthV2Control proxyControl =
+ getRequestControl(ProxiedAuthV2Control.DECODER);
+
+ Entry authorizationEntry = proxyControl.getAuthorizationEntry();
+ setAuthorizationEntry(authorizationEntry);
+ if (authorizationEntry == null)
+ {
+ setProxiedAuthorizationDN(DN.nullDN());
+ }
+ else
+ {
+ setProxiedAuthorizationDN(authorizationEntry.getDN());
+ }
+ }
+ else if (oid.equals(OID_PERSISTENT_SEARCH))
+ {
+ PersistentSearchControl psearchControl =
+ getRequestControl(PersistentSearchControl.DECODER);
+
+ persistentSearch = new PersistentSearch(this,
+ psearchControl.getChangeTypes(),
+ psearchControl.getReturnECs());
+
+ // If we're only interested in changes, then we don't actually want
+ // to process the search now.
+ if (psearchControl.getChangesOnly())
+ {
+ changesOnly = true;
+ }
+ }
+ else if (oid.equals(OID_LDAP_SUBENTRIES))
+ {
+ setReturnLDAPSubentries(true);
+ }
+ else if (oid.equals(OID_MATCHED_VALUES))
+ {
+ MatchedValuesControl matchedValuesControl =
+ getRequestControl(MatchedValuesControl.DECODER);
+ setMatchedValuesControl(matchedValuesControl);
+ }
+ else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
+ {
+ setIncludeUsableControl(true);
+ }
+ else if (oid.equals(OID_REAL_ATTRS_ONLY))
+ {
+ setRealAttributesOnly(true);
+ }
+ else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
+ {
+ setVirtualAttributesOnly(true);
+ }
+ else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
+ DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
+ {
+ // Do nothing here and let AciHandler deal with it.
+ }
+
+ // NYI -- Add support for additional controls.
+
+ else if (c.isCritical())
+ {
+ if ((replicationServer == null) || (! supportsControl(oid)))
+ {
+ throw new DirectoryException(
+ ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
+ ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
+ }
+ }
+ }
+ }
+ }
+
+ private void processSearch()
+ throws DirectoryException, CanceledOperationException
+ {
+ startECLSessionMsg = new StartECLSessionMsg();
+ startECLSessionMsg.setECLRequestType(
+ StartECLSessionMsg.REQUEST_TYPE_FROM_COOKIE);
+ startECLSessionMsg.setChangeNumber(
+ new ChangeNumber(TimeThread.getTime(),(short)0, (short)0));
+ startECLSessionMsg.setCrossDomainServerState(requestCookie.toString());
+
+ if (persistentSearch==null)
+ startECLSessionMsg.setPersistent(StartECLSessionMsg.NON_PERSISTENT);
+ else
+ if (!changesOnly)
+ startECLSessionMsg.setPersistent(StartECLSessionMsg.PERSISTENT);
+ else
+ startECLSessionMsg.setPersistent(
+ StartECLSessionMsg.PERSISTENT_CHANGES_ONLY);
+
+ startECLSessionMsg.setFirstDraftChangeNumber(0);
+ startECLSessionMsg.setLastDraftChangeNumber(0);
+
+ // Help correlate with access log with the format: "conn=x op=y msgID=z"
+ startECLSessionMsg.setOperationId(
+ "conn="+String.valueOf(this.getConnectionID())
+ + " op="+String.valueOf(this.getOperationID())
+ + " msgID="+String.valueOf(getOperationID()));
+
+ startECLSessionMsg.setExcludedDNs(
+ MultimasterReplication.getPrivateDomains());
+
+ // Start session
+ eclSession = replicationServer.createECLSession(startECLSessionMsg);
+
+ if (!getScope().equals(SearchScope.SINGLE_LEVEL))
+ {
+ // Root entry
+ Entry entry = createRootEntry();
+ if (matchFilter(entry))
+ returnEntry(entry, null);
+ }
+
+ if (true)
+ {
+ // Loop on result entries
+ int INITIAL=0;
+ int PSEARCH=1;
+ int phase=INITIAL;
+ while (true)
+ {
+
+ // Check for a request to cancel this operation.
+ checkIfCanceled(false);
+
+ ECLUpdateMsg update = eclSession.getNextUpdate();
+ if (update!=null)
+ {
+ if (phase==INITIAL)
+ buildAndReturnEntry(update);
+ }
+ else
+ {
+ if (phase==INITIAL)
+ {
+ if (this.persistentSearch == null)
+ {
+ eclSession.close();
+ break;
+ }
+ else
+ {
+ phase=PSEARCH;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean supportsControl(String oid)
+ {
+ return ((supportedControls != null) &&
+ supportedControls.contains(oid));
+ }
+
+ /**
+ * Build an ECL entry from a provided ECL msg and return it.
+ * @param eclmsg The provided ECL msg.
+ * @throws DirectoryException When an errors occurs.
+ */
+ private void buildAndReturnEntry(ECLUpdateMsg eclmsg)
+ throws DirectoryException
+ {
+ Entry entry = null;
+
+ // build and filter
+ entry = createEntryFromMsg(eclmsg);
+ if (matchFilter(entry))
+ {
+ List<Control> controls = new ArrayList<Control>(0);
+
+ EntryChangelogNotificationControl clrc
+ = new EntryChangelogNotificationControl(
+ true,eclmsg.getCookie().toString());
+ controls.add(clrc);
+ boolean entryReturned = returnEntry(entry, controls);
+ if (entryReturned==false)
+ {
+ // FIXME:ECL Handle the error case where returnEntry fails
+ }
+ }
+ }
+
+ /**
+ * Test if the provided entry matches the filter, base and scope.
+ * @param entry The provided entry
+ * @return whether the entry matches.
+ * @throws DirectoryException When a problem occurs.
+ */
+ private boolean matchFilter(Entry entry)
+ throws DirectoryException
+ {
+ boolean ms = entry.matchesBaseAndScope(getBaseDN(), getScope());
+ boolean mf = getFilter().matchesEntry(entry);
+ return (ms && mf);
+ }
+
+ /**
+ * Create an ECL entry from a provided ECL msg.
+ *
+ * @param eclmsg the provided ECL msg.
+ * @return the created ECL entry.
+ * @throws DirectoryException When an error occurs.
+ */
+ public static Entry createEntryFromMsg(ECLUpdateMsg eclmsg)
+ throws DirectoryException
+ {
+ Entry clEntry = null;
+
+ // Get the meat fro the ecl msg
+ UpdateMsg msg = eclmsg.getUpdateMsg();
+
+ if (msg instanceof AddMsg)
+ {
+ AddMsg addMsg = (AddMsg)msg;
+
+ // Map the addMsg to an LDIF string for the 'changes' attribute
+ String LDIFchanges = addMsgToLDIFString(addMsg);
+
+ clEntry = createChangelogEntry(
+ eclmsg.getServiceId(),
+ eclmsg.getCookie().toString(),
+ DN.decode(addMsg.getDn()),
+ addMsg.getChangeNumber(),
+ LDIFchanges, // entry as created (in LDIF format)
+ addMsg.getUniqueId(),
+ null, // real time current entry
+ null, // real time attrs names
+ null, // hist entry attributes
+ 0, // TODO:ECL G Good changelog draft compat. addMsg.getSeqnum()
+ "add");
+
+ } else
+ if (msg instanceof ModifyMsg)
+ {
+ ModifyMsg modMsg = (ModifyMsg)msg;
+ InternalClientConnection conn =
+ InternalClientConnection.getRootConnection();
+ try
+ {
+ // Map the modMsg modifications to an LDIF string
+ // for the 'changes' attribute of the CL entry
+ ModifyOperation modifyOperation =
+ (ModifyOperation)modMsg.createOperation(conn);
+ String LDIFchanges = modToLDIF(modifyOperation.getModifications());
+
+ // TODO:ECL G Good changelog draft compat. Hist entry attributes
+ // ArrayList<RawAttribute> attributes = modMsg.getEntryAttributes();
+ clEntry = createChangelogEntry(
+ eclmsg.getServiceId(),
+ eclmsg.getCookie().toString(),
+ DN.decode(modMsg.getDn()),
+ modMsg.getChangeNumber(),
+ LDIFchanges,
+ modMsg.getUniqueId(),
+ null, // real time current entry
+ null, // real time attrs names
+ null, // hist entry attributes
+ 0, // TODO:ECL G Good changelog draft compat. modMsg.getSeqnum()
+ "modify");
+
+ }
+ catch(Exception e)
+ {
+ // FIXME:ECL Handle error when createOperation raise DataFormatExceptin
+ }
+ }
+ else if (msg instanceof ModifyDNMsg)
+ {
+ ModifyDNMsg modDNMsg = (ModifyDNMsg)msg;
+
+ clEntry = createChangelogEntry(
+ eclmsg.getServiceId(),
+ eclmsg.getCookie().toString(),
+ DN.decode(modDNMsg.getDn()),
+ modDNMsg.getChangeNumber(),
+ null,
+ modDNMsg.getUniqueId(),
+ null, // real time current entry
+ null, // real time attrs names
+ null, // hist entry attributes
+ 0, // TODO:ECL G Good changelog draft compat. modDNMsg.getSeqnum()
+ "modrdn");
+
+ Attribute a = Attributes.create("newrdn", modDNMsg.getNewRDN());
+ clEntry.addAttribute(a, null);
+
+ Attribute b = Attributes.create("newsuperior", modDNMsg.getNewSuperior());
+ clEntry.addAttribute(b, null);
+
+ Attribute c = Attributes.create("deleteoldrdn",
+ String.valueOf(modDNMsg.deleteOldRdn()));
+ clEntry.addAttribute(c, null);
+
+ }
+ else if (msg instanceof DeleteMsg)
+ {
+ DeleteMsg delMsg = (DeleteMsg)msg;
+ ArrayList<RawAttribute> rattributes = new ArrayList<RawAttribute>();
+ String delAttrs = delMsgToLDIFString(rattributes);
+
+ clEntry = createChangelogEntry(
+ eclmsg.getServiceId(),
+ eclmsg.getCookie().toString(),
+ DN.decode(delMsg.getDn()),
+ delMsg.getChangeNumber(),
+ delAttrs,
+ delMsg.getUniqueId(),
+ null,
+ null,
+ null, //rattributes,
+ 0, // TODO:ECL G Good changelog draft compat. delMsg.getSeqnum()
+ "delete");
+ }
+ return clEntry;
+ }
+
+ /**
+ * Creates the root entry of the external changelog.
+ * @return The root entry created.
+ */
+ private Entry createRootEntry()
+ {
+ HashMap<ObjectClass,String> oclasses =
+ new LinkedHashMap<ObjectClass,String>(3);
+ oclasses.putAll(eclObjectClasses);
+
+ HashMap<AttributeType,List<Attribute>> userAttrs =
+ new LinkedHashMap<AttributeType,List<Attribute>>();
+
+ HashMap<AttributeType,List<Attribute>> operationalAttrs =
+ new LinkedHashMap<AttributeType,List<Attribute>>();
+
+ // Add to the root entry the replication state of each domain
+ // TODO:ECL Put in ECL root entry, the ServerState for each domain
+ AttributeType descType = DirectoryServer.getAttributeType("description");
+ List<ReplicationDomain> supportedReplicationDomains
+ = new ArrayList<ReplicationDomain>();
+
+ for (ReplicationDomain domain : supportedReplicationDomains)
+ {
+ // Crappy stuff to return the server state to the client
+ LinkedList<Attribute> attrList = new LinkedList<Attribute>();
+ attrList.add(Attributes.create("description",
+ domain.getServiceID() + "/" + domain.getServerState().toString()));
+ userAttrs.put(descType, attrList);
+ }
+ Entry e = new Entry(this.rootBaseDN, oclasses, userAttrs,
+ operationalAttrs);
+ return e;
+ }
+
+ /**
+ * Create an ECL entry.
+ * @param serviceID The provided cookie value.
+ * @param cookie The provided cookie value.
+ * @param targetDN The provided targetDN.
+ * @param changeNumber The provided replication changeNumber.
+ * @param clearLDIFchanges The provided LDIF changes for ADD and MODIFY
+ * @param targetUUID The provided targetUUID.
+ * @param entry The provided related current entry.
+ * @param targetAttrNames The provided list of attributes names that should
+ * be read from the entry (real time values)
+ * @param histEntryAttributes TODO:ECL Adress hist entry attributes
+ * @param seqnum The provided RCL int change number.
+ * @param changetype The provided change type (add, ...)
+ * @return The created ECL entry.
+ * @throws DirectoryException
+ * When any error occurs.
+ */
+ public static Entry createChangelogEntry(
+ String serviceID,
+ String cookie,
+ DN targetDN,
+ ChangeNumber changeNumber,
+ String clearLDIFchanges,
+ String targetUUID,
+ Entry entry,
+ List<String> targetAttrNames,
+ List<RawAttribute> histEntryAttributes,
+ int seqnum,
+ String changetype)
+ throws DirectoryException
+ {
+ String dnString = "cn="+ changeNumber +"," +
+ serviceID + "," +
+ ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT;
+
+ HashMap<ObjectClass,String> oClasses =
+ new LinkedHashMap<ObjectClass,String>(3);
+ oClasses.putAll(eclObjectClasses);
+
+ ObjectClass extensibleObjectOC =
+ DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
+ oClasses.put(extensibleObjectOC, OC_EXTENSIBLE_OBJECT);
+
+ HashMap<AttributeType,List<Attribute>> uAttrs =
+ new LinkedHashMap<AttributeType,List<Attribute>>();
+
+ HashMap<AttributeType,List<Attribute>> operationalAttrs =
+ new LinkedHashMap<AttributeType,List<Attribute>>();
+
+ ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+
+ // TODO:ECL G Good changelog compat.
+ if (seqnum>0)
+ {
+ Attribute a = Attributes.create("changeNumber", String.valueOf(seqnum));
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+ }
+
+ //
+ Attribute a = Attributes.create("replicationCSN", changeNumber.toString());
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+
+ //
+ SimpleDateFormat dateFormat;
+ dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // ??
+ a = Attributes.create(
+ DirectoryServer.getAttributeType("changeTime", true),
+ dateFormat.format(new Date(changeNumber.getTime())));
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+
+ /* Change time in a friendly format
+ Date date = new Date(changeNumber.getTime());
+ a = Attributes.create("clearChangeTime", date.toString());
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+ */
+
+ //
+ a = Attributes.create("changeType", changetype);
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+
+ //
+ a = Attributes.create(
+ DirectoryServer.getAttributeType("targetdn", true),
+ targetDN.toNormalizedString());
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+
+ //
+ a = Attributes.create("replicaIdentifier",
+ Short.toString(changeNumber.getServerId()));
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+
+ if (clearLDIFchanges != null)
+ {
+ if (changetype.equalsIgnoreCase("delete"))
+ {
+ a = Attributes.create("clearDeletedEntryAttrs", clearLDIFchanges);
+ }
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ operationalAttrs.put(a.getAttributeType(), attrList);
+
+ if (changetype.equalsIgnoreCase("delete"))
+ {
+ a = Attributes.create("deletedentryattrs",
+ clearLDIFchanges + "\n"); // force base64
+ }
+ else
+ {
+ AttributeType at = DirectoryServer.getAttributeType("changes");
+ a = Attributes.create(at, clearLDIFchanges + "\n"); // force base64
+ }
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+ }
+
+ a = Attributes.create("targetentryuuid", targetUUID);
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ operationalAttrs.put(a.getAttributeType(), attrList);
+
+ a = Attributes.create("cookie", cookie);
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ operationalAttrs.put(a.getAttributeType(), attrList);
+
+ // entryAttribute version
+ /*
+ if (targetAttrNames != null)
+ {
+ String sEntryAttrs = null;
+ for (String attrName : targetAttrNames)
+ {
+ List<Attribute> attrs = entry.getAttribute(attrName);
+ for (Attribute attr : attrs)
+ {
+ if (sEntryAttrs==null)
+ sEntryAttrs="";
+ else
+ sEntryAttrs+=";";
+ sEntryAttrs += attr.toString();
+ }
+ }
+ if (sEntryAttrs!=null)
+ {
+ a = Attributes.create("entryAttributes", sEntryAttrs);
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(a);
+ uAttrs.put(a.getAttributeType(), attrList);
+ }
+ }
+ */
+
+ /*
+ if (targetAttrNames != null)
+ {
+ for (String attrName : targetAttrNames)
+ {
+ String newName = "target"+attrName;
+ List<Attribute> attrs = entry.getAttribute(attrName);
+ for (Attribute aa : attrs)
+ {
+ AttributeBuilder builder = new AttributeBuilder(
+ DirectoryServer.getDefaultAttributeType(newName));
+ builder.setOptions(aa.getOptions());
+ builder.addAll(aa);
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(builder.toAttribute());
+ uAttrs.put(aa.getAttributeType(), attrList);
+ }
+ }
+ }
+ */
+ /* TODO: Implement entry attributes historical values
+ if (histEntryAttributes != null)
+ {
+ for (RawAttribute rea : histEntryAttributes)
+ {
+ // uAttrs.put(ea.getAttributeType(), null);
+ // FIXME: ERRONEOUS TYPING !!!!!!!!!!!!!
+ try
+ {
+ String newName = "target"+rea.getAttributeType();
+ AttributeType nat=DirectoryServer.getDefaultAttributeType(newName);
+ rea.setAttributeType(newName);
+ attrList = new ArrayList<Attribute>(1);
+ attrList.add(rea.toAttribute());
+ uAttrs.put(nat, attrList);
+ }
+ catch(Exception e)
+ {
+
+ }
+ }
+ }
+ */
+
+ // at the end build the CL entry to be returned
+ Entry cle = new Entry(
+ DN.decode(dnString),
+ eclObjectClasses,
+ uAttrs,
+ operationalAttrs);
+
+ return cle;
+ }
+
+ private static String addMsgToLDIFString(AddMsg addMsg)
+ {
+ StringBuilder modTypeLine = new StringBuilder();
+ // LinkedList<StringBuilder> ldifLines =
+ // new LinkedList<StringBuilder>();
+
+ try
+ {
+ AddOperation addOperation = (AddOperation)addMsg.createOperation(
+ InternalClientConnection.getRootConnection());
+
+ Map<AttributeType,List<Attribute>> attributes =
+ new HashMap<AttributeType,List<Attribute>>();
+
+ for (RawAttribute a : addOperation.getRawAttributes())
+ {
+ Attribute attr = a.toAttribute();
+ AttributeType attrType = attr.getAttributeType();
+ List<Attribute> attrs = attributes.get(attrType);
+ if (attrs == null)
+ {
+ attrs = new ArrayList<Attribute>(1);
+ attrs.add(attr);
+ attributes.put(attrType, attrs);
+ }
+ else
+ {
+ attrs.add(attr);
+ }
+ }
+ for (List<Attribute> attrList : attributes.values())
+ {
+ for (Attribute a : attrList)
+ {
+ StringBuilder attrName = new StringBuilder(a.getName());
+ for (String o : a.getOptions())
+ {
+ attrName.append(";");
+ attrName.append(o);
+ }
+ for (AttributeValue av : a)
+ {
+ String stringValue = av.toString();
+
+ modTypeLine.append(attrName);
+ if (needsBase64Encoding(stringValue))
+ {
+ modTypeLine.append(":: ");
+ modTypeLine.append(Base64.encode(av.getValue()));
+ }
+ else
+ {
+ modTypeLine.append(": ");
+ modTypeLine.append(stringValue);
+ }
+ modTypeLine.append("\n");
+ }
+ }
+ }
+ return modTypeLine.toString();
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ return null;
+ }
+
+ /**
+ * q.
+ * @param rattributes q.
+ * @return
+ */
+ private static String delMsgToLDIFString(ArrayList<RawAttribute> rattributes)
+ {
+ StringBuilder modTypeLine = new StringBuilder();
+ try
+ {
+
+ Map<AttributeType,List<Attribute>> attributes =
+ new HashMap<AttributeType,List<Attribute>>();
+
+ for (RawAttribute a : rattributes)
+ {
+ Attribute attr = a.toAttribute();
+ AttributeType attrType = attr.getAttributeType();
+ List<Attribute> attrs = attributes.get(attrType);
+ if (attrs == null)
+ {
+ attrs = new ArrayList<Attribute>(1);
+ attrs.add(attr);
+ attributes.put(attrType, attrs);
+ }
+ else
+ {
+ attrs.add(attr);
+ }
+ }
+
+ for (List<Attribute> attrList : attributes.values())
+ {
+ for (Attribute a : attrList)
+ {
+ StringBuilder attrName = new StringBuilder(a.getName());
+ for (String o : a.getOptions())
+ {
+ attrName.append(";");
+ attrName.append(o);
+ }
+ for (AttributeValue av : a)
+ {
+ // ??
+ String stringValue = av.toString();
+ modTypeLine.append(attrName);
+ if (needsBase64Encoding(stringValue))
+ {
+ modTypeLine.append(":: ");
+ modTypeLine.append(Base64.encode(av.getValue()));
+ }
+ else
+ {
+ modTypeLine.append(": ");
+ modTypeLine.append(stringValue);
+ }
+ modTypeLine.append("\n");
+ }
+ }
+ }
+ return modTypeLine.toString();
+ }
+ catch(Exception e)
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ return null;
+ }
+
+ /**
+ * Dumps a list of modifications into an LDIF string.
+ * @param mods The provided list of modifications.
+ * @return The LDIF string.
+ */
+ public static String modToLDIF(List<Modification> mods)
+ {
+ StringBuilder modTypeLine = new StringBuilder();
+ Iterator<Modification> iterator = mods.iterator();
+ while (iterator.hasNext())
+ {
+ Modification m = iterator.next();
+ Attribute a = m.getAttribute();
+ String attrName = a.getName();
+ modTypeLine.append(m.getModificationType().getLDIFName());
+ modTypeLine.append(": ");
+ modTypeLine.append(attrName);
+ modTypeLine.append("\n");
+
+ Iterator<AttributeValue> iteratorValues = a.iterator();
+ while (iteratorValues.hasNext())
+ {
+ AttributeValue av = iteratorValues.next();
+
+ String stringValue = av.toString();
+
+ modTypeLine.append(attrName);
+ if (needsBase64Encoding(stringValue))
+ {
+ modTypeLine.append(":: ");
+ modTypeLine.append(Base64.encode(av.getValue()));
+ }
+ else
+ {
+ modTypeLine.append(": ");
+ modTypeLine.append(stringValue);
+ }
+ modTypeLine.append("\n");
+ }
+
+ modTypeLine.append("-");
+ if (iterator.hasNext())
+ {
+ modTypeLine.append("\n");
+ }
+ }
+ return modTypeLine.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CancelResult cancel(CancelRequest cancelRequest)
+ {
+ if (eclSession!=null)
+ {
+ try
+ {
+ eclSession.close();
+ }
+ catch(Exception e){}
+ }
+ return super.cancel(cancelRequest);
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
new file mode 100644
index 0000000..8581738
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/ECLWorkflowElement.java
@@ -0,0 +1,220 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.workflowelement.externalchangelog;
+
+
+
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.opends.server.admin.std.server.WorkflowElementCfg;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.PersistentSearch;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.replication.server.ReplicationServer;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Operation;
+import org.opends.server.workflowelement.LeafWorkflowElement;
+
+
+
+
+/**
+ * This class defines a workflow element for the external changelog (ECL);
+ * e-g an entity that handles the processing of an operation against the ECL.
+ */
+public class ECLWorkflowElement extends
+ LeafWorkflowElement<WorkflowElementCfg>
+{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = getTracer();
+
+ /**
+ *The set of persistent searches registered with this work flow element.
+ */
+ private final List<PersistentSearch> persistentSearches =
+ new CopyOnWriteArrayList<PersistentSearch>();
+
+ /**
+ * A string indicating the type of the workflow element.
+ */
+ public static final String ECL_WORKFLOW_ELEMENT = "EXTERNAL CHANGE LOG";
+
+ /**
+ * The replication server object to which we will submits request
+ * on the ECL. Retrieved from the local DirectoryServer.
+ */
+ private ReplicationServer replicationServer;
+
+ /**
+ * Creates a new instance of the External Change Log workflow element.
+ * @param rs the provided replication server
+ * @throws DirectoryException If the ECL workflow is already registered.
+ */
+ public ECLWorkflowElement(ReplicationServer rs)
+ throws DirectoryException
+ {
+ this.replicationServer =rs;
+ super.initialize(ECL_WORKFLOW_ELEMENT, ECL_WORKFLOW_ELEMENT);
+ DirectoryServer.registerWorkflowElement(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void finalizeWorkflowElement()
+ {
+ // null all fields so that any use of the finalized object will raise
+ // an NPE
+ super.initialize(ECL_WORKFLOW_ELEMENT, null);
+
+ // Cancel all persistent searches.
+ for (PersistentSearch psearch : persistentSearches) {
+ psearch.cancel();
+ }
+ persistentSearches.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void execute(Operation operation) throws CanceledOperationException {
+ switch (operation.getOperationType())
+ {
+ case SEARCH:
+ ECLSearchOperation searchOperation =
+ new ECLSearchOperation((SearchOperation) operation);
+ searchOperation.processECLSearch(this);
+ break;
+ case ABANDON:
+ // There is no processing for an abandon operation.
+ break;
+
+ case BIND:
+ case ADD:
+ case DELETE:
+ case MODIFY:
+ case MODIFY_DN:
+ case COMPARE:
+ default:
+ throw new AssertionError("Attempted to execute an invalid operation " +
+ "type: " + operation.getOperationType() +
+ " (" + operation + ")");
+ }
+ }
+
+
+
+ /**
+ * Attaches the current local operation to the global operation so that
+ * operation runner can execute local operation post response later on.
+ *
+ * @param <O> subtype of Operation
+ * @param <L> subtype of LocalBackendOperation
+ * @param globalOperation the global operation to which local operation
+ * should be attached to
+ * @param currentLocalOperation the local operation to attach to the global
+ * operation
+ */
+ @SuppressWarnings("unchecked")
+ public static final <O extends Operation,L> void
+ attachLocalOperation (O globalOperation, L currentLocalOperation)
+ {
+ List<?> existingAttachment =
+ (List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
+
+ List<L> newAttachment = new ArrayList<L>();
+
+ if (existingAttachment != null)
+ {
+ // This line raises an unchecked conversion warning.
+ // There is nothing we can do to prevent this warning
+ // so let's get rid of it since we know the cast is safe.
+ newAttachment.addAll ((List<L>) existingAttachment);
+ }
+ newAttachment.add (currentLocalOperation);
+ globalOperation.setAttachment(Operation.LOCALBACKENDOPERATIONS,
+ newAttachment);
+ }
+
+ /**
+ * Registers the provided persistent search operation with this
+ * local backend workflow element so that it will be notified of any
+ * add, delete, modify, or modify DN operations that are performed.
+ *
+ * @param persistentSearch
+ * The persistent search operation to register with this
+ * local backend workflow element.
+ */
+ void registerPersistentSearch(PersistentSearch persistentSearch)
+ {
+ PersistentSearch.CancellationCallback callback =
+ new PersistentSearch.CancellationCallback()
+ {
+ public void persistentSearchCancelled(PersistentSearch psearch)
+ {
+ psearch.getSearchOperation().cancel(null);
+ persistentSearches.remove(psearch);
+ }
+ };
+
+ persistentSearches.add(persistentSearch);
+ persistentSearch.registerCancellationCallback(callback);
+ }
+
+
+
+ /**
+ * Gets the list of persistent searches currently active against
+ * this local backend workflow element.
+ *
+ * @return The list of persistent searches currently active against
+ * this local backend workflow element.
+ */
+ public List<PersistentSearch> getPersistentSearches()
+ {
+ return persistentSearches;
+ }
+
+ /**
+ * Returns the associated replication server.
+ * @return the rs.
+ */
+ public ReplicationServer getReplicationServer()
+ {
+ return this.replicationServer;
+ }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java
new file mode 100644
index 0000000..5ea48f2
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/externalchangelog/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+
+
+
+/**
+ * This package contains source for the local backend workflow element, which
+ * are used to process operations against data stored in local backend databases
+ * and other repositories that are considered "local".
+ */
+@org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE)
+package org.opends.server.workflowelement.externalchangelog;
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java
new file mode 100644
index 0000000..62d082d
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/controls/ExternalChangelogControlTest.java
@@ -0,0 +1,100 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.controls;
+
+import static org.opends.server.util.ServerConstants.OID_ECL_COOKIE_EXCHANGE_CONTROL;
+import static org.opends.server.util.ServerConstants.OID_PERSISTENT_SEARCH;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.protocols.ldap.LDAPControl;
+import org.opends.server.protocols.ldap.LDAPReader;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.ByteStringBuilder;
+import org.opends.server.types.DirectoryException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test ChangeNumber and ChangeNumberGenerator
+ */
+public class ExternalChangelogControlTest
+ extends ControlsTestCase
+{
+
+ /**
+ * Create values for External Changelog Request Control
+ */
+ @DataProvider(name = "eclRequestControl")
+ public Object[][] createECLRequestControlTest()
+ {
+
+ return new Object[][]
+ {
+ {true, "" },
+ {false, "o=test:;" },
+ {false, "o=test:000001210b6f21e904b100000002;" },
+ {false, "o=test:000001210b6f21e904b100000001;o=test2:000001210b6f21e904b100000002;" },
+ {false, "o=test:000001210b6f21e904b100000001 000001210b6f21e904b200000001;o=test2:000001210b6f21e904b100000002 000001210b6f21e904b200000002;" },
+ };
+ }
+
+ /**
+ * Test ExternalChangelogRequestControl
+ */
+ @Test(dataProvider = "eclRequestControl")
+ public void checkECLRequestControlTest(boolean critical, String value)
+ throws Exception
+ {
+ // Test contructor
+ MultiDomainServerState mdss = new MultiDomainServerState(value);
+ ExternalChangelogRequestControl eclrc
+ = new ExternalChangelogRequestControl(critical, mdss);
+ assertNotNull(eclrc);
+ assertEquals(critical, eclrc.isCritical());
+ assertEquals(OID_ECL_COOKIE_EXCHANGE_CONTROL, eclrc.getOID());
+ assertTrue(eclrc.getCookie().equalsTo(mdss));
+
+ // Test encode/decode
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ ASN1Writer writer = ASN1.getWriter(bsb);
+ bsb.clear();
+ eclrc.write(writer);
+ LDAPControl control = LDAPReader.readControl(ASN1.getReader(bsb));
+ eclrc =
+ ExternalChangelogRequestControl.DECODER.decode(control.isCritical(), control.getValue());
+ assertNotNull(eclrc);
+ assertEquals(critical, eclrc.isCritical());
+ assertEquals(OID_ECL_COOKIE_EXCHANGE_CONTROL, eclrc.getOID());
+ assertTrue(eclrc.getCookie().equalsTo(mdss),
+ "Expect:"+value+", Got:"+eclrc.getCookie().toString());
+ }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
index a6fd622..6a692eb 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -158,11 +158,6 @@
// This test suite depends on having the schema available.
TestCaseUtils.startServer();
- // After start of server because not seen if server not started
- // after server started once, TestCaseUtils.startServer() does nothing.
- logError(Message.raw(Category.SYNC, Severity.NOTICE,
- " ##### Calling ReplicationTestCase.setUp ##### "));
-
// Initialize the test backend (TestCaseUtils.TEST_ROOT_DN_STRING)
// (in case previous (non replication?) tests were run before...)
TestCaseUtils.initializeTestBackend(true);
@@ -265,7 +260,60 @@
return broker;
}
- /**
+ /**
+ * Open an ECL replicationServer session to the local ReplicationServer
+ protected ReplicationBroker openECLReplicationSession(
+ int window_size, int port, int timeout, boolean emptyOldChanges,
+ Short serverId)
+ throws Exception, SocketException
+ {
+ ServerState state = new ServerState();
+
+ //if (emptyOldChanges)
+ // new PersistentServerState(baseDn, serverId, new ServerState());
+
+ ReplicationBroker broker = new ReplicationBroker(null,
+ state, "cn=changelog", serverId, window_size,
+ -1, 100000, getReplSessionSecurity(), (byte)1);
+ ArrayList<String> servers = new ArrayList<String>(1);
+ servers.add("localhost:" + port);
+ broker.start(servers);
+ if (timeout != 0)
+ broker.setSoTimeout(timeout);
+ checkConnection(30, broker, port); // give some time to the broker to connect
+ // to the replicationServer.
+ if (emptyOldChanges)
+ {
+ // loop receiving update until there is nothing left
+ // to make sure that message from previous tests have been consumed.
+ try
+ {
+ while (true)
+ {
+ ReplicationMsg rMsg = broker.receive();
+ if (rMsg instanceof ErrorMsg)
+ {
+ ErrorMsg eMsg = (ErrorMsg)rMsg;
+ logError(new MessageBuilder(
+ "ReplicationTestCase/openReplicationSession ").append(
+ " received ErrorMessage when emptying old changes ").append(
+ eMsg.getDetails()).toMessage());
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ logError(new MessageBuilder(
+ "ReplicationTestCase/openReplicationSession ").append(e.getMessage())
+ .append(" when emptying old changes").toMessage());
+ }
+ }
+ return broker;
+ }
+ */
+
+
+ /**
* Check connection of the provided ds to the
* replication server. Waits for connection to be ok up to secTimeout seconds
* before failing.
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
index e2e27bf..96b5209 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/SchemaReplicationTest.java
@@ -22,7 +22,7 @@
* CDDL HEADER END
*
*
- * Copyright 2008 Sun Microsystems, Inc.
+ * Copyright 2008-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication;
@@ -210,7 +210,7 @@
* Checks that changes to the schema pushed to the replicationServer
* are received and correctly replayed by replication plugin.
*/
- @Test(dependsOnMethods = { "pushSchemaChange" })
+ @Test(enabled=true,dependsOnMethods = { "pushSchemaChange" })
public void replaySchemaChange() throws Exception
{
logError(Message.raw(Category.SYNC, Severity.NOTICE,
@@ -341,5 +341,7 @@
{
broker.stop();
}
+ logError(Message.raw(Category.SYNC, Severity.NOTICE,
+ "Ending replication test : pushSchemaFilesChange "));
}
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java
new file mode 100644
index 0000000..1e69126
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/common/ExternalChangeLogTest.java
@@ -0,0 +1,1569 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.server.replication.common;
+
+import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.replication.protocol.OperationContext.SYNCHROCONTEXT;
+import static org.opends.server.util.StaticUtils.createEntry;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.opends.messages.Category;
+import org.opends.messages.Message;
+import org.opends.messages.Severity;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.api.Backend;
+import org.opends.server.api.ConnectionHandler;
+import org.opends.server.api.SynchronizationProvider;
+import org.opends.server.backends.MemoryBackend;
+import org.opends.server.config.ConfigException;
+import org.opends.server.controls.ExternalChangelogRequestControl;
+import org.opends.server.controls.PersistentSearchChangeType;
+import org.opends.server.controls.PersistentSearchControl;
+import org.opends.server.core.AddOperationBasis;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyDNOperationBasis;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.plugins.InvocationCounterPlugin;
+import org.opends.server.protocols.asn1.ASN1Exception;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.protocols.ldap.BindRequestProtocolOp;
+import org.opends.server.protocols.ldap.BindResponseProtocolOp;
+import org.opends.server.protocols.ldap.LDAPAttribute;
+import org.opends.server.protocols.ldap.LDAPConnectionHandler;
+import org.opends.server.protocols.ldap.LDAPConstants;
+import org.opends.server.protocols.ldap.LDAPFilter;
+import org.opends.server.protocols.ldap.LDAPMessage;
+import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.protocols.ldap.LDAPStatistics;
+import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
+import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
+import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.plugin.DomainFakeCfg;
+import org.opends.server.replication.plugin.LDAPReplicationDomain;
+import org.opends.server.replication.plugin.MultimasterReplication;
+import org.opends.server.replication.protocol.AddMsg;
+import org.opends.server.replication.protocol.DeleteMsg;
+import org.opends.server.replication.protocol.DoneMsg;
+import org.opends.server.replication.protocol.ECLUpdateMsg;
+import org.opends.server.replication.protocol.ModifyDNMsg;
+import org.opends.server.replication.protocol.ModifyDnContext;
+import org.opends.server.replication.protocol.ModifyMsg;
+import org.opends.server.replication.protocol.ReplicationMsg;
+import org.opends.server.replication.protocol.UpdateMsg;
+import org.opends.server.replication.server.ReplServerFakeConfiguration;
+import org.opends.server.replication.server.ReplicationServer;
+import org.opends.server.replication.server.ReplicationServerDomain;
+import org.opends.server.replication.service.ReplicationBroker;
+import org.opends.server.tools.LDAPWriter;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.Control;
+import org.opends.server.types.DN;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.LDAPException;
+import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.RDN;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchScope;
+import org.opends.server.util.LDIFWriter;
+import org.opends.server.util.TimeThread;
+import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for the replicationServer code.
+ */
+
+public class ExternalChangeLogTest extends ReplicationTestCase
+{
+ // The tracer object for the debug logger
+ private static final DebugTracer TRACER = getTracer();
+
+ // The replicationServer that will be used in this test.
+ private ReplicationServer replicationServer = null;
+
+ // The port of the replicationServer.
+ private int replicationServerPort;
+
+ public static final String TEST_ROOT_DN_STRING2 = "o=test2";
+ public static final String TEST_BACKEND_ID2 = "test2";
+
+ // The LDAPStatistics object associated with the LDAP connection handler.
+ protected LDAPStatistics ldapStatistics;
+
+ /**
+ * Set up the environment for performing the tests in this Class.
+ * Replication
+ *
+ * @throws Exception
+ * If the environment could not be set up.
+ */
+ @BeforeClass
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ // This test suite depends on having the schema available.
+ configure();
+ }
+
+ /**
+ * Utility : configure a replicationServer.
+ */
+ protected void configure() throws Exception
+ {
+ // Find a free port for the replicationServer
+ ServerSocket socket = TestCaseUtils.bindFreePort();
+ replicationServerPort = socket.getLocalPort();
+ socket.close();
+
+ /*
+ // Add the replication server to the configuration
+ TestCaseUtils.dsconfig(
+ "create-replication-server",
+ "--provider-name", "Multimaster Synchronization",
+ "--set", "replication-db-directory:" + "externalChangeLogTestConfigureDb",
+ "--set", "replication-port:" + replicationServerPort,
+ "--set", "replication-server-id:71");
+
+ // Retrieves the replicationServer object
+ DirectoryServer.getSynchronizationProviders();
+ for (SynchronizationProvider<?> provider : DirectoryServer
+ .getSynchronizationProviders()) {
+ if (provider instanceof MultimasterReplication) {
+ MultimasterReplication mmp = (MultimasterReplication) provider;
+ ReplicationServerListener list = mmp.getReplicationServerListener();
+ if (list != null) {
+ replicationServer = list.getReplicationServer();
+ if (replicationServer != null) {
+ break;
+ }
+ }
+ }
+ }
+ */
+ ReplServerFakeConfiguration conf1 =
+ new ReplServerFakeConfiguration(
+ replicationServerPort, "ExternalChangeLogTestDb",
+ 0, 71, 0, 100, null);
+
+ replicationServer = new ReplicationServer(conf1);;
+ debugInfo("configure", "ReplicationServer created"+replicationServer);
+
+ }
+
+ /**
+ * Launcher.
+ */
+ @Test(enabled=true)
+ public void ECLReplicationServerTest()
+ {
+ ECLRemoteEmpty();replicationServer.clearDb();
+ ECLDirectEmpty();replicationServer.clearDb();
+ ECLDirectAllOps();replicationServer.clearDb();
+ ECLRemoteNonEmpty();replicationServer.clearDb();
+ ECLDirectMsg();replicationServer.clearDb();
+ ECLDirectPsearch(true);replicationServer.clearDb();
+ ECLDirectPsearch(false);replicationServer.clearDb();
+ ECLPrivateDirectMsg();replicationServer.clearDb();
+ // TODO:ECL Test SEARCH abandon and check everything shutdown and cleaned
+ // TODO:ECL Test PSEARCH abandon and check everything shutdown and cleaned
+ // TODO:ECL Test invalid DN in cookie returns UNWILLING + message
+ // TODO:ECL Test notif control returned contains the cookie
+ // TODO:ECL Test the attributes list and values returned in ECL entries
+ // TODO:ECL Test search -s base, -s one
+ }
+
+ //=======================================================
+ // From 3 remote ECL session,
+ // Test DoneMsg is received from 1 suffix on each session.
+ private void ECLRemoteEmpty()
+ {
+ String tn = "ECLRemoteEmpty";
+ debugInfo(tn, "Starting test\n\n");
+
+ ReplicationBroker server1 = null;
+ ReplicationBroker server2 = null;
+ ReplicationBroker server3 = null;
+
+ try
+ {
+ // Create 3 ECL broker
+ server1 = openReplicationSession(
+ DN.decode("cn=changelog"), (short)1111,
+ 100, replicationServerPort, 1000, false);
+ assertTrue(server1.isConnected());
+ server2 = openReplicationSession(
+ DN.decode("cn=changelog"), (short)2222,
+ 100, replicationServerPort,1000, false);
+ assertTrue(server2.isConnected());
+ server3 = openReplicationSession(
+ DN.decode("cn=changelog"), (short)3333,
+ 100, replicationServerPort,1000, false);
+ assertTrue(server3.isConnected());
+
+ // Test broker1 receives only Done
+ ReplicationMsg msg;
+ int msgc=0;
+ do
+ {
+ msg = server1.receive();
+ msgc++;
+ }
+ while(!(msg instanceof DoneMsg));
+ assertTrue(msgc==1,
+ "Ending " + tn + " with incorrect message type :" +
+ msg.getClass().getCanonicalName());
+ assertTrue(msg instanceof DoneMsg);
+
+ // Test broker2 receives only Done
+ msgc=0;
+ do
+ {
+ msg = server2.receive();
+ msgc++;
+ }
+ while(!(msg instanceof DoneMsg));
+ assertTrue(msgc==1,
+ "Ending " + tn + " with incorrect message type :" +
+ msg.getClass().getCanonicalName());
+
+ // Test broker3 receives only Done
+ msgc=0;
+ do
+ {
+ msg = server3.receive();
+ msgc++;
+ }
+ while(!(msg instanceof DoneMsg));
+ assertTrue(msgc==1,
+ "Ending " + tn + " with incorrect message type :" +
+ msg.getClass().getCanonicalName());
+
+ server1.stop();
+ server2.stop();
+ server3.stop();
+ sleep(500);
+ debugInfo(tn, "Ending test successfully\n\n");
+ }
+ catch(Exception e)
+ {
+ debugInfo(tn, "Ending test with exception:"
+ + stackTraceToSingleLineString(e));
+ assertTrue(e == null);
+ }
+ }
+
+ //=======================================================
+ // From 1 remote ECL session,
+ // test simple update to be received from 2 suffixes
+ private void ECLRemoteNonEmpty()
+ {
+ String tn = "ECLRemoteNonEmpty";
+ debugInfo(tn, "Starting test\n\n");
+
+ replicationServer.clearDb();
+
+ // create a broker
+ ReplicationBroker server01 = null;
+ ReplicationBroker server02 = null;
+ ReplicationBroker serverECL = null;
+
+ try
+ {
+ // create 2 reguler brokers on the 2 suffixes
+ server01 = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING), (short) 1201,
+ 100, replicationServerPort,
+ 1000, true);
+
+ server02 = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING2), (short) 1202,
+ 100, replicationServerPort,
+ 1000, true, EMPTY_DN_GENID);
+
+ // create and publish 1 change on each suffix
+ long time = TimeThread.getTime();
+ int ts = 1;
+ ChangeNumber cn1 = new ChangeNumber(time, ts++, (short)1201);
+ DeleteMsg delMsg1 =
+ new DeleteMsg("o=" + tn + "1," + TEST_ROOT_DN_STRING, cn1, "ECLBasicMsg1uid");
+ server01.publish(delMsg1);
+ debugInfo(tn, "publishes:" + delMsg1);
+
+ ChangeNumber cn2 = new ChangeNumber(time, ts++, (short)1202);
+ DeleteMsg delMsg2 =
+ new DeleteMsg("o=" + tn + "2," + TEST_ROOT_DN_STRING2, cn2, "ECLBasicMsg2uid");
+ server02.publish(delMsg2);
+ debugInfo(tn, "publishes:" + delMsg2);
+
+ // wait for the server to take these changes into account
+ sleep(500);
+
+ // open ECL broker
+ serverECL = openReplicationSession(
+ DN.decode("cn=changelog"), (short)10,
+ 100, replicationServerPort, 1000, false);
+ assertTrue(serverECL.isConnected());
+
+ // receive change 1 from suffix 1
+ ReplicationMsg msg;
+ msg = serverECL.receive();
+ ECLUpdateMsg eclu = (ECLUpdateMsg)msg;
+ UpdateMsg u = eclu.getUpdateMsg();
+ debugInfo(tn, "RESULT:" + u.getChangeNumber());
+ assertTrue(u.getChangeNumber().equals(cn1), "RESULT:" + u.getChangeNumber());
+ assertTrue(eclu.getCookie().equalsTo(new MultiDomainServerState(
+ "o=test:"+delMsg1.getChangeNumber()+";")));
+
+ // receive change 2 from suffix 2
+ msg = serverECL.receive();
+ eclu = (ECLUpdateMsg)msg;
+ u = eclu.getUpdateMsg();
+ debugInfo(tn, "RESULT:" + u.getChangeNumber());
+ assertTrue(u.getChangeNumber().equals(cn2), "RESULT:" + u.getChangeNumber());
+ assertTrue(eclu.getCookie().equalsTo(new MultiDomainServerState(
+ "o=test2:"+delMsg2.getChangeNumber()+";"+
+ "o=test:"+delMsg1.getChangeNumber()+";")));
+
+ // receive Done
+ msg = serverECL.receive();
+ debugInfo(tn, "RESULT:" + msg);
+ assertTrue(msg instanceof DoneMsg, "RESULT:" + msg);
+
+ // clean
+ serverECL.stop();
+ server01.stop();
+ server02.stop();
+ sleep(2000);
+ debugInfo(tn, "Ending test successfully");
+ }
+ catch(Exception e)
+ {
+ debugInfo(tn, "Ending test with exception:"
+ + stackTraceToSingleLineString(e));
+ assertTrue(e == null);
+ }
+ }
+
+ /**
+ * From embedded ECL (no remote session)
+ * With empty RS, simple search should return only root entry.
+ */
+ private void ECLDirectEmpty()
+ {
+ String tn = "ECLDirectEmpty";
+ debugInfo(tn, "Starting test\n\n");
+
+ try
+ {
+ // search on 'cn=changelog'
+ InternalSearchOperation op = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ LDAPFilter.decode("(objectclass=*)"));
+
+ debugInfo(tn, "Res code=" + op.getResultCode());
+ debugInfo(tn, "Res OE=" + ResultCode.OPERATIONS_ERROR);
+ // Error because no cookie
+ assertEquals(
+ op.getResultCode(), ResultCode.OPERATIONS_ERROR,
+ op.getErrorMessage().toString());
+
+ // search on 'cn=changelog'
+ InternalSearchOperation op2 = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, 0,
+ false,
+ LDAPFilter.decode("(objectclass=*)"),
+ new LinkedHashSet<String>(0),
+ getControls(""),
+ null);
+
+ // success
+ assertEquals(
+ op2.getResultCode(), ResultCode.SUCCESS,
+ op2.getErrorMessage().toString());
+
+ // root entry returned
+ assertEquals(op2.getEntriesSent(), 1);
+ debugInfo(tn, "Ending test successfully");
+ }
+ catch(LDAPException e)
+ {
+ debugInfo(tn, "Ending test with exception e="
+ + stackTraceToSingleLineString(e));
+ assertTrue(e == null);
+ }
+ }
+
+ private ArrayList<Control> getControls(String cookie)
+ {
+ ExternalChangelogRequestControl control =
+ new ExternalChangelogRequestControl(true,
+ new MultiDomainServerState(cookie));
+ ArrayList<Control> controls = new ArrayList<Control>(0);
+ controls.add(control);
+ return controls;
+ }
+
+ // Utility - creates an LDIFWriter to dump result entries
+ private static LDIFWriter getLDIFWriter()
+ {
+ LDIFWriter ldifWriter = null;
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ LDIFExportConfig exportConfig = new LDIFExportConfig(stream);
+ try
+ {
+ ldifWriter = new LDIFWriter(exportConfig);
+ }
+ catch (Exception e)
+ {
+ assert(e==null);
+ }
+ return ldifWriter;
+ }
+
+ // Add an entry in the database
+ private void addEntry(Entry entry) throws Exception
+ {
+ AddOperationBasis addOp = new AddOperationBasis(connection,
+ InternalClientConnection.nextOperationID(), InternalClientConnection
+ .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
+ entry.getUserAttributes(), entry.getOperationalAttributes());
+ addOp.setInternalOperation(true);
+ addOp.run();
+ assertEquals(addOp.getResultCode(), ResultCode.SUCCESS,
+ addOp.getErrorMessage().toString() + addOp.getAdditionalLogMessage());
+ assertNotNull(getEntry(entry.getDN(), 1000, true));
+ }
+
+ //=======================================================
+ // From embebbded ECL
+ // Search ECL with 1 domain, public then private backend
+ private void ECLPrivateDirectMsg()
+ {
+ ReplicationServer replServer = null;
+ String tn = "ECLPrivateDirectMsg";
+ debugInfo(tn, "Starting test");
+
+ try
+ {
+ // Initialize a second test backend
+ Backend backend2 = initializeTestBackend2(false);
+
+ DN baseDn = DN.decode(TEST_ROOT_DN_STRING2);
+
+ // configure and start replication of TEST_ROOT_DN_STRING on the server
+ SortedSet<String> replServers = new TreeSet<String>();
+ replServers.add("localhost:"+replicationServerPort);
+ DomainFakeCfg domainConf =
+ new DomainFakeCfg(baseDn, (short) 1602, replServers);
+ LDAPReplicationDomain domain = MultimasterReplication.createNewDomain(domainConf);
+ SynchronizationProvider replicationPlugin = new MultimasterReplication();
+ replicationPlugin.completeSynchronizationProvider();
+ sleep(1000);
+
+ Entry e = createEntry(baseDn);
+ addEntry(e);
+ sleep(1000);
+
+ ExternalChangelogRequestControl control =
+ new ExternalChangelogRequestControl(true,
+ new MultiDomainServerState(""));
+ ArrayList<Control> controls = new ArrayList<Control>(0);
+ controls.add(control);
+
+ // search on 'cn=changelog'
+ LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+ attributes.add("+");
+ attributes.add("*");
+ InternalSearchOperation searchOp = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*)"),
+ attributes,
+ controls,
+ null);
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+
+ LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+ assertTrue(entries != null);
+ if (entries != null)
+ {
+ for (SearchResultEntry resultEntry : entries)
+ {
+ debugInfo(tn, "Result private entry=" + resultEntry.toLDIFString());
+ }
+ }
+ assertEquals(entries.size(),1, "Entries number returned by search");
+
+ // Same with private backend
+ domain.getBackend().setPrivateBackend(true);
+
+ searchOp = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*)"),
+ attributes,
+ controls,
+ null);
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+
+ entries = searchOp.getSearchEntries();
+ assertTrue(entries != null);
+ assertTrue(entries.size()==0);
+
+ if (replServer != null)
+ replServer.remove();
+
+ if (domain != null)
+ MultimasterReplication.deleteDomain(baseDn);
+
+ if (replicationPlugin != null)
+ DirectoryServer.deregisterSynchronizationProvider(replicationPlugin);
+
+ removeTestBackend2(backend2);
+ }
+ catch(Exception e)
+ {
+ debugInfo(tn, "Ending test ECLDirectMsg with exception:\n"
+ + stackTraceToSingleLineString(e));
+ assertTrue(e == null);
+ }
+ debugInfo(tn, "Ending test successfully");
+ }
+
+ //=======================================================
+ // From embebbded ECL
+ // Search ECL with 4 messages on 2 suffixes from 2 brokers
+ private void ECLDirectMsg()
+ {
+ String tn = "ECLDirectMsg";
+ debugInfo(tn, "Starting test");
+
+ try
+ {
+ // Initialize a second test backend
+ Backend backend2 = initializeTestBackend2(true);
+
+ //
+ LDIFWriter ldifWriter = getLDIFWriter();
+
+ // --
+ ReplicationBroker s1test = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING), (short) 1201,
+ 100, replicationServerPort,
+ 1000, true);
+
+ ReplicationBroker s2test2 = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING2), (short) 1202,
+ 100, replicationServerPort,
+ 1000, true, EMPTY_DN_GENID);
+ sleep(500);
+
+ // Produce updates
+ long time = TimeThread.getTime();
+ int ts = 1;
+ ChangeNumber cn = new ChangeNumber(time, ts++, s1test.getServerId());
+ DeleteMsg delMsg =
+ new DeleteMsg("uid="+tn+"1," + TEST_ROOT_DN_STRING, cn, tn+"uuid1");
+ s1test.publish(delMsg);
+ debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+ cn = new ChangeNumber(time++, ts++, s2test2.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"2," + TEST_ROOT_DN_STRING2, cn, tn+"uuid2");
+ s2test2.publish(delMsg);
+ debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+ cn = new ChangeNumber(time++, ts++, s2test2.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"3," + TEST_ROOT_DN_STRING2, cn, tn+"uuid3");
+ s2test2.publish(delMsg);
+ debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+ cn = new ChangeNumber(time++, ts++, s1test.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"4," + TEST_ROOT_DN_STRING, cn, tn+"uuid4");
+ s1test.publish(delMsg);
+ debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+ sleep(500);
+
+ // Changes are :
+ // s1 s2
+ // o=test msg1/msg4
+ // o=test2 msg2/msg2
+ String cookie= "";
+
+ debugInfo(tn, "STEP 1 - from empty cookie("+cookie+")");
+
+ // search on 'cn=changelog'
+ LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+ attributes.add("+");
+ attributes.add("*");
+ InternalSearchOperation searchOp =
+ connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*direct*)"),
+ attributes,
+ getControls(cookie),
+ null);
+
+ // We expect SUCCESS and the 4 changes
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString());
+
+ LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+ if (entries != null)
+ {
+ int i=0;
+ for (SearchResultEntry entry : entries)
+ {
+ debugInfo(tn, " RESULT entry returned:" + entry.toSingleLineString());
+ ldifWriter.writeEntry(entry);
+ try
+ {
+ if (i++==2)
+ cookie =
+ entry.getAttribute("cookie").get(0).iterator().next().toString();
+ }
+ catch(NullPointerException e)
+ {}
+ }
+ }
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
+
+ // We expect the 4 changes
+ assertEquals(searchOp.getSearchEntries().size(), 4);
+
+ // Now start from last cookie and expect to get the last change
+ // search on 'cn=changelog'
+ attributes = new LinkedHashSet<String>();
+ attributes.add("+");
+ attributes.add("*");
+
+ debugInfo(tn, "STEP 2 - from cookie" + cookie);
+
+ ExternalChangelogRequestControl control =
+ new ExternalChangelogRequestControl(true,
+ new MultiDomainServerState(cookie));
+ ArrayList<Control> controls = new ArrayList<Control>(0);
+ controls.add(control);
+
+ searchOp = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*direct*)"),
+ attributes,
+ controls,
+ null);
+
+
+ // We expect SUCCESS and the 4th change
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+
+ cookie= "";
+ entries = searchOp.getSearchEntries();
+ if (entries != null)
+ {
+ for (SearchResultEntry entry : entries)
+ {
+ debugInfo(tn, "Result entry=\n" + entry.toLDIFString());
+ ldifWriter.writeEntry(entry);
+ try
+ {
+ cookie =
+ entry.getAttribute("cookie").get(0).iterator().next().toString();
+ }
+ catch(NullPointerException e)
+ {}
+ }
+ }
+
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
+
+ // we expect msg4
+ assertEquals(searchOp.getSearchEntries().size(), 1);
+
+ debugInfo(tn, "STEP 3 - from cookie" + cookie);
+
+ cn = new ChangeNumber(time++, ts++, s1test.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"5," + TEST_ROOT_DN_STRING, cn, tn+"uuid5");
+ s1test.publish(delMsg);
+ sleep(500);
+
+ // Changes are :
+ // s1 s2
+ // o=test msg1,msg5 msg4
+ // o=test2 msg3 msg2
+
+ control =
+ new ExternalChangelogRequestControl(true,
+ new MultiDomainServerState(cookie));
+ controls = new ArrayList<Control>(0);
+ controls.add(control);
+
+ searchOp = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*direct*)"),
+ attributes,
+ controls,
+ null);
+
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+
+ cookie= "";
+ entries = searchOp.getSearchEntries();
+ if (entries != null)
+ {
+ for (SearchResultEntry resultEntry : entries)
+ {
+ debugInfo(tn, "Result entry=\n" + resultEntry.toLDIFString());
+ ldifWriter.writeEntry(resultEntry);
+ try
+ {
+ cookie =
+ resultEntry.getAttribute("cookie").get(0).iterator().next().toString();
+ }
+ catch(NullPointerException e)
+ {}
+ }
+ }
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
+
+ // we expect 1 entries : msg5
+ assertEquals(searchOp.getSearchEntries().size(), 1);
+
+ cookie="";
+ debugInfo(tn, "STEP 4 - [filter:o=test cookie:" + cookie + "]");
+
+ control =
+ new ExternalChangelogRequestControl(true,
+ new MultiDomainServerState(cookie));
+ controls = new ArrayList<Control>(0);
+ controls.add(control);
+
+ searchOp = connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*direct*,o=test)"),
+ attributes,
+ controls,
+ null);
+
+
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString() + searchOp.getAdditionalLogMessage());
+
+ entries = searchOp.getSearchEntries();
+ if (entries != null)
+ {
+ for (SearchResultEntry resultEntry : entries)
+ {
+ debugInfo(tn, "Result entry=\n" + resultEntry.toLDIFString());
+ ldifWriter.writeEntry(resultEntry);
+ try
+ {
+ cookie =
+ resultEntry.getAttribute("cookie").get(0).iterator().next().toString();
+ }
+ catch(NullPointerException e)
+ {}
+ }
+ }
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS);
+
+ // we expect msg1 + msg4 + msg5
+ assertEquals(searchOp.getSearchEntries().size(), 3);
+
+ // --
+ ReplicationBroker s1test2 = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING2), (short) 1203,
+ 100, replicationServerPort,
+ 1000, true, EMPTY_DN_GENID);
+
+ ReplicationBroker s2test = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING), (short) 1204,
+ 100, replicationServerPort,
+ 1000, true);
+ sleep(500);
+
+ // Test startState of the domain for the first cookie feature
+ time = TimeThread.getTime();
+ cn = new ChangeNumber(time++, ts++, s1test2.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"6," + TEST_ROOT_DN_STRING2, cn, tn+"uuid6");
+ s1test2.publish(delMsg);
+
+ cn = new ChangeNumber(time++, ts++, s2test.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"7," + TEST_ROOT_DN_STRING, cn, tn+"uuid7");
+ s2test.publish(delMsg);
+
+ cn = new ChangeNumber(time++, ts++, s1test2.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"8," + TEST_ROOT_DN_STRING2, cn, tn+"uuid8");
+ s1test2.publish(delMsg);
+
+ cn = new ChangeNumber(time++, ts++, s2test.getServerId());
+ delMsg =
+ new DeleteMsg("uid="+tn+"9," + TEST_ROOT_DN_STRING, cn, tn+"uuid9");
+ s2test.publish(delMsg);
+ sleep(500);
+
+ ReplicationServerDomain rsd =
+ replicationServer.getReplicationServerDomain(TEST_ROOT_DN_STRING, false);
+ ServerState startState = rsd.getStartState();
+ assertTrue(startState.getMaxChangeNumber(s1test.getServerId()).getSeqnum()==1);
+ assertTrue(startState.getMaxChangeNumber(s2test.getServerId()).getSeqnum()==7);
+
+ rsd =
+ replicationServer.getReplicationServerDomain(TEST_ROOT_DN_STRING2, false);
+ startState = rsd.getStartState();
+ assertTrue(startState.getMaxChangeNumber(s2test2.getServerId()).getSeqnum()==2);
+ assertTrue(startState.getMaxChangeNumber(s1test2.getServerId()).getSeqnum()==6);
+
+ s1test.stop();
+ s1test2.stop();
+ s2test.stop();
+ s2test2.stop();
+
+ removeTestBackend2(backend2);
+ }
+ catch(Exception e)
+ {
+ debugInfo(tn, "Ending test ECLDirectMsg with exception:\n"
+ + stackTraceToSingleLineString(e));
+ assertTrue(e == null);
+ }
+ debugInfo(tn, "Ending test successfully");
+ }
+
+ // simple update to be received
+ private void ECLDirectAllOps()
+ {
+ String tn = "ECLDirectAllOps";
+ debugInfo(tn, "Starting test\n\n");
+
+ try
+ {
+ LDIFWriter ldifWriter = getLDIFWriter();
+
+ // Creates broker on o=test
+ ReplicationBroker server01 = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING), (short) 1201,
+ 100, replicationServerPort,
+ 1000, true);
+ int ts = 1;
+
+ String user1entryUUID = "11111111-1111-1111-1111-111111111111";
+ String baseUUID = "22222222-2222-2222-2222-222222222222";
+
+
+ // Publish DEL
+ ChangeNumber cn1 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+ DeleteMsg delMsg =
+ new DeleteMsg("uid="+tn+"1," + TEST_ROOT_DN_STRING, cn1, tn+"uuid1");
+ server01.publish(delMsg);
+ debugInfo(tn, " publishes " + delMsg.getChangeNumber());
+
+ // Publish ADD
+ ChangeNumber cn2 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+ String lentry = new String("dn: uid="+tn+"2," + TEST_ROOT_DN_STRING + "\n"
+ + "objectClass: top\n" + "objectClass: domain\n"
+ + "entryUUID: "+user1entryUUID+"\n");
+ Entry entry = TestCaseUtils.entryFromLdifString(lentry);
+ AddMsg addMsg = new AddMsg(
+ cn2,
+ "uid="+tn+"2," + TEST_ROOT_DN_STRING,
+ user1entryUUID,
+ baseUUID,
+ entry.getObjectClassAttribute(),
+ entry.getAttributes(),
+ new ArrayList<Attribute>());
+ server01.publish(addMsg);
+ debugInfo(tn, " publishes " + addMsg.getChangeNumber());
+
+ // Publish MOD
+ ChangeNumber cn3 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+ Attribute attr1 = Attributes.create("description", "new value");
+ Modification mod1 = new Modification(ModificationType.REPLACE, attr1);
+ List<Modification> mods = new ArrayList<Modification>();
+ mods.add(mod1);
+ ModifyMsg modMsg = new ModifyMsg(cn3, DN
+ .decode("uid="+tn+"3," + TEST_ROOT_DN_STRING), mods, tn+"uuid3");
+ server01.publish(modMsg);
+ debugInfo(tn, " publishes " + modMsg.getChangeNumber());
+
+ // Publish modDN
+ ChangeNumber cn4 = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+ ModifyDNOperationBasis op = new ModifyDNOperationBasis(connection, 1, 1, null,
+ DN.decode("uid="+tn+"4," + TEST_ROOT_DN_STRING), // entryDN
+ RDN.decode("uid="+tn+"new4"), // new rdn
+ true, // deleteoldrdn
+ DN.decode(TEST_ROOT_DN_STRING2)); // new superior
+ op.setAttachment(SYNCHROCONTEXT, new ModifyDnContext(cn4, tn+"uuid4",
+ "newparentId"));
+ LocalBackendModifyDNOperation localOp = new LocalBackendModifyDNOperation(op);
+ ModifyDNMsg modDNMsg = new ModifyDNMsg(localOp);
+ server01.publish(modDNMsg);
+ debugInfo(tn, " publishes " + modDNMsg.getChangeNumber());
+ sleep(600);
+
+ String cookie= "";
+
+ // search on 'cn=changelog'
+ debugInfo(tn, "STEP 1 - from empty cookie("+cookie+")");
+ LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+ attributes.add("+");
+ attributes.add("*");
+
+ ExternalChangelogRequestControl control =
+ new ExternalChangelogRequestControl(true,
+ new MultiDomainServerState());
+ ArrayList<Control> controls = new ArrayList<Control>(0);
+ controls.add(control);
+
+ InternalSearchOperation searchOp =
+ connection.processSearch(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, // Size limit
+ 0, // Time limit
+ false, // Types only
+ LDAPFilter.decode("(targetDN=*"+tn+"*,o=test)"),
+ attributes,
+ controls,
+ null);
+ sleep(500);
+
+ // test success
+ assertEquals(searchOp.getResultCode(), ResultCode.SUCCESS,
+ searchOp.getErrorMessage().toString());
+
+ // test 4 entries returned
+ LinkedList<SearchResultEntry> entries = searchOp.getSearchEntries();
+ // 4 entries expected
+ assertEquals(searchOp.getSearchEntries().size(), 4);
+ if (entries != null)
+ {
+ int i=0;
+ for (SearchResultEntry resultEntry : entries)
+ {
+ i++;
+ debugInfo(tn, "Result entry returned:" + resultEntry.toLDIFString());
+ ldifWriter.writeEntry(resultEntry);
+ if (i==1)
+ {
+ // check the DEL entry has the right content
+ checkValue(resultEntry,"replicationcsn",cn1.toString());
+ checkValue(resultEntry,"replicaidentifier","1201");
+ checkValue(resultEntry,"targetdn","uid="+tn+"1," + TEST_ROOT_DN_STRING);
+ checkValue(resultEntry,"changetype","delete");
+ checkValue(resultEntry,"cookie","o=test:"+cn1.toString()+";");
+ checkValue(resultEntry,"targetentryuuid",tn+"uuid1");
+ } else if (i==2)
+ {
+ // check the ADD entry has the right content
+ String expectedValue1 = "objectClass: domain\nobjectClass: top\n" +
+ "entryUUID: 11111111-1111-1111-1111-111111111111\n\n";
+ String expectedValue2 = "entryUUID: 11111111-1111-1111-1111-111111111111\n" +
+ "objectClass: domain\nobjectClass: top\n\n";
+ checkPossibleValues(resultEntry,"changes",expectedValue1, expectedValue2);
+ checkValue(resultEntry,"replicationcsn",cn2.toString());
+ checkValue(resultEntry,"replicaidentifier","1201");
+ checkValue(resultEntry,"targetdn","uid="+tn+"2," + TEST_ROOT_DN_STRING);
+ checkValue(resultEntry,"changetype","add");
+ checkValue(resultEntry,"cookie","o=test:"+cn2.toString()+";");
+ checkValue(resultEntry,"targetentryuuid",user1entryUUID);
+
+ } else if (i==3)
+ {
+ // check the MOD entry has the right content
+ String expectedValue = "replace: description\n" +
+ "description: new value\n-\n";
+ checkValue(resultEntry,"changes",expectedValue);
+ checkValue(resultEntry,"replicationcsn",cn3.toString());
+ checkValue(resultEntry,"replicaidentifier","1201");
+ checkValue(resultEntry,"targetdn","uid="+tn+"3," + TEST_ROOT_DN_STRING);
+ checkValue(resultEntry,"changetype","modify");
+ checkValue(resultEntry,"cookie","o=test:"+cn3.toString()+";");
+ checkValue(resultEntry,"targetentryuuid",tn+"uuid3");
+ } else if (i==4)
+ {
+ // check the MODDN entry has the right content
+ checkValue(resultEntry,"replicationcsn",cn4.toString());
+ checkValue(resultEntry,"replicaidentifier","1201");
+ checkValue(resultEntry,"targetdn","uid="+tn+"4," + TEST_ROOT_DN_STRING);
+ checkValue(resultEntry,"changetype","modrdn");
+ checkValue(resultEntry,"cookie","o=test:"+cn4.toString()+";");
+ checkValue(resultEntry,"targetentryuuid",tn+"uuid4");
+ checkValue(resultEntry,"newrdn","uid=ECLDirectAllOpsnew4");
+ checkValue(resultEntry,"newsuperior",TEST_ROOT_DN_STRING2);
+ checkValue(resultEntry,"deleteoldrdn","true");
+ }
+ }
+ }
+ server01.stop();
+ }
+ catch(Exception e)
+ {
+ debugInfo(tn, "Ending test with exception:\n"
+ + stackTraceToSingleLineString(e));
+ assertTrue(e == null);
+ }
+ debugInfo(tn, "Ending test with success");
+ }
+
+ private static void checkValue(Entry entry, String attrName, String expectedValue)
+ {
+ AttributeValue av = null;
+ try
+ {
+ List<Attribute> attrs = entry.getAttribute(attrName);
+ Attribute a = attrs.iterator().next();
+ av = a.iterator().next();
+ String encodedValue = av.toString();
+ assertTrue(encodedValue.equalsIgnoreCase(expectedValue),
+ "In entry " + entry + " attr <" + attrName + "> equals " +
+ av + " instead of expected value " + expectedValue);
+ }
+ catch(Exception e)
+ {
+ assertTrue(false,
+ "In entry " + entry + " attr <" + attrName + "> equals " +
+ av + " instead of expected value " + expectedValue);
+ }
+ }
+
+ private static void checkPossibleValues(Entry entry, String attrName,
+ String expectedValue1, String expectedValue2)
+ {
+ AttributeValue av = null;
+ try
+ {
+ List<Attribute> attrs = entry.getAttribute(attrName);
+ Attribute a = attrs.iterator().next();
+ av = a.iterator().next();
+ String encodedValue = av.toString();
+ assertTrue(
+ (encodedValue.equalsIgnoreCase(expectedValue1) ||
+ encodedValue.equalsIgnoreCase(expectedValue2)),
+ "In entry " + entry + " attr <" + attrName + "> equals " +
+ av + " instead of one of the expected values " + expectedValue1
+ + " or " + expectedValue2);
+ }
+ catch(Exception e)
+ {
+ assertTrue(false,
+ "In entry " + entry + " attr <" + attrName + "> equals " +
+ av + " instead of one of the expected values " + expectedValue1
+ + " or " + expectedValue2);
+ }
+ }
+
+ /**
+ * Test persistent search
+ */
+ private void ECLDirectPsearch(boolean changesOnly)
+ {
+ String tn = "ECLDirectPsearch_" + String.valueOf(changesOnly);
+ debugInfo(tn, "Starting test \n\n");
+ Socket s =null;
+
+ // create stats
+ for (ConnectionHandler ch : DirectoryServer.getConnectionHandlers())
+ {
+ if (ch instanceof LDAPConnectionHandler)
+ {
+ LDAPConnectionHandler lch = (LDAPConnectionHandler) ch;
+ if (!lch.useSSL())
+ {
+ ldapStatistics = lch.getStatTracker();
+ }
+ }
+ }
+ assertNotNull(ldapStatistics);
+
+ try
+ {
+ // Create broker on suffix
+ ReplicationBroker server01 = openReplicationSession(
+ DN.decode(TEST_ROOT_DN_STRING), (short) 1201,
+ 100, replicationServerPort,
+ 1000, true);
+ int ts = 1;
+
+ // Produce update on this suffix
+ ChangeNumber cn = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+ DeleteMsg delMsg =
+ new DeleteMsg("uid=" + tn + "1," + TEST_ROOT_DN_STRING, cn, tn+"uuid1");
+ debugInfo(tn, " publishing " + delMsg.getChangeNumber());
+ server01.publish(delMsg);
+ this.sleep(500); // let's be sure the message is in the RS
+
+ // Creates cookie control
+ ArrayList<Control> controls = getControls("");
+
+ // Creates psearch control
+ HashSet<PersistentSearchChangeType> changeTypes =
+ new HashSet<PersistentSearchChangeType>();
+ changeTypes.add(PersistentSearchChangeType.ADD);
+ changeTypes.add(PersistentSearchChangeType.DELETE);
+ changeTypes.add(PersistentSearchChangeType.MODIFY);
+ changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
+ boolean returnECs = true;
+ PersistentSearchControl persSearchControl = new PersistentSearchControl(
+ changeTypes, changesOnly, returnECs);
+ controls.add(persSearchControl);
+
+ // Creates request
+ SearchRequestProtocolOp searchRequest =
+ new SearchRequestProtocolOp(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE,
+ false,
+ LDAPFilter.decode("(targetDN=*"+tn+"*,o=test)"),
+ null);
+
+ // Connects and bind
+ s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+ org.opends.server.tools.LDAPReader r = new org.opends.server.tools.LDAPReader(s);
+ LDAPWriter w = new LDAPWriter(s);
+ s.setSoTimeout(1500000);
+ bindAsManager(w, r);
+
+ // Since we are going to be watching the post-response count, we need to
+ // wait for the server to become idle before kicking off the next request
+ // to ensure that any remaining post-response processing from the previous
+ // operation has completed.
+ assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000));
+
+ InvocationCounterPlugin.resetAllCounters();
+
+ long searchRequests = ldapStatistics.getSearchRequests();
+ long searchEntries = ldapStatistics.getSearchResultEntries();
+ long searchReferences = ldapStatistics.getSearchResultReferences();
+ long searchesDone = ldapStatistics.getSearchResultsDone();
+
+ debugInfo(tn, "Sending the PSearch request filter=(targetDN=*"+tn+"*,o=test)");
+ LDAPMessage message;
+ message = new LDAPMessage(2, searchRequest, controls);
+ w.writeMessage(message);
+ this.sleep(500);
+
+ SearchResultEntryProtocolOp searchResultEntry = null;
+ SearchResultDoneProtocolOp searchResultDone = null;
+
+ if (changesOnly == false)
+ {
+ // Wait for change 1
+ debugInfo(tn, "Waiting for : INIT search expected to return change 1");
+ searchEntries = 0;
+ message = null;
+
+ try
+ {
+ while ((searchEntries<1) && (message = r.readMessage()) != null)
+ {
+ debugInfo(tn, "First search returns " +
+ message.getProtocolOpType() + message + " " + searchEntries);
+ switch (message.getProtocolOpType())
+ {
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+ searchResultEntry = message.getSearchResultEntryProtocolOp();
+ searchEntries++;
+ break;
+
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+ searchReferences++;
+ break;
+
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+ searchResultDone = message.getSearchResultDoneProtocolOp();
+ assertEquals(
+ searchResultDone.getResultCode(), ResultCode.SUCCESS,
+ searchResultDone.getErrorMessage().toString());
+// assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+ searchesDone++;
+ break;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ debugInfo(tn, "INIT search failed with e=" +
+ stackTraceToSingleLineString(e));
+ }
+ debugInfo(tn, "INIT search done with success. searchEntries="
+ + searchEntries + " searchesDone="+ searchesDone);
+ }
+
+ // Produces change 2
+ cn = new ChangeNumber(TimeThread.getTime(), ts++, (short)1201);
+ String expectedDn = "uid=" + tn + "2," + TEST_ROOT_DN_STRING;
+ delMsg = new DeleteMsg(expectedDn, cn, tn + "uuid2");
+ debugInfo(tn, " publishing " + delMsg.getChangeNumber());
+ server01.publish(delMsg);
+ this.sleep(1000);
+
+ debugInfo(tn, delMsg.getChangeNumber() +
+ " published , will wait for new entries (Persist)");
+
+ // wait for the 1 new entry
+ searchEntries = 0;
+ searchResultEntry = null;
+ searchResultDone = null;
+ message = null;
+ while ((searchEntries<1) && (message = r.readMessage()) != null)
+ {
+ debugInfo(tn, "2nd search returns " +
+ message.getProtocolOpType() + message);
+ switch (message.getProtocolOpType())
+ {
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+ searchResultEntry = message.getSearchResultEntryProtocolOp();
+ searchEntries++;
+ break;
+
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+ searchReferences++;
+ break;
+
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+ searchResultDone = message.getSearchResultDoneProtocolOp();
+ assertEquals(
+ searchResultDone.getResultCode(), ResultCode.SUCCESS,
+ searchResultDone.getErrorMessage().toString());
+// assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+ searchesDone++;
+ break;
+ }
+ }
+ sleep(1000);
+
+ // Check we received change 2
+ for (LDAPAttribute a : searchResultEntry.getAttributes())
+ {
+ if (a.getAttributeType().equalsIgnoreCase("targetDN"))
+ {
+ for (ByteString av : a.getValues())
+ {
+ assertTrue(av.toString().equalsIgnoreCase(expectedDn),
+ "Entry returned by psearch is " + av.toString() +
+ " when expected is " + expectedDn);
+ }
+ }
+ }
+ debugInfo(tn, "Second search done successfully : " + searchResultEntry);
+ server01.stop();
+ try { s.close(); } catch (Exception e) {};
+ sleep(1000);
+
+ // TODO: Testing ACI is disabled because it is currently failing when
+ // ran in the precommit target while it works well when running alone.
+ // anonymous search returns entries from cn=changelog whereas it
+ // should not. Probably a previous test in the nightlytests suite is
+ // removing/modifying some ACIs...
+ // When problem found, we have to re-enable this test.
+ if (false)
+ {
+
+ // ACI step
+ debugInfo(tn, "Starting ACI step");
+ s = new Socket("127.0.0.1", TestCaseUtils.getServerLdapPort());
+ r = new org.opends.server.tools.LDAPReader(s);
+ w = new LDAPWriter(s);
+ s.setSoTimeout(1500000);
+ bindAsWhoEver(w, r, "toto", "tutu", LDAPResultCode.OPERATIONS_ERROR);
+
+ searchRequest =
+ new SearchRequestProtocolOp(
+ ByteString.valueOf("cn=changelog"),
+ SearchScope.WHOLE_SUBTREE,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ Integer.MAX_VALUE,
+ Integer.MAX_VALUE,
+ false,
+ LDAPFilter.decode("(targetDN=*directpsearch*,o=test)"),
+ null);
+
+ debugInfo(tn, "ACI test : sending search");
+ message = new LDAPMessage(2, searchRequest, getControls(""));
+ w.writeMessage(message);
+
+ searchesDone=0;
+ searchEntries = 0;
+ searchResultEntry = null;
+ searchResultDone = null;
+ while ((searchesDone==0) && (message = r.readMessage()) != null)
+ {
+ debugInfo(tn, "ACI test : message returned " +
+ message.getProtocolOpType() + message);
+ switch (message.getProtocolOpType())
+ {
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
+ searchResultEntry = message.getSearchResultEntryProtocolOp();
+ //assertTrue(false, "Unexpected entry returned in ACI test of " + tn + searchResultEntry);
+ searchEntries++;
+ break;
+
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
+ searchReferences++;
+ break;
+
+ case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
+ searchResultDone = message.getSearchResultDoneProtocolOp();
+ assertEquals(searchResultDone.getResultCode(),
+ ResultCode.SUCCESS.getIntValue());
+// assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+ searchesDone++;
+ break;
+ }
+ }
+ // search should end with success
+ assertTrue(searchesDone==1);
+ // but returning no entry
+ assertEquals(searchEntries,0, "Bad search entry# in ACI test of " + tn);
+ }
+
+ try { s.close(); } catch (Exception e) {};
+ sleep(1000);
+ }
+ catch(Exception e)
+ {
+ assertTrue(e==null, stackTraceToSingleLineString(e));
+ }
+ debugInfo(tn, "Ends test successfuly");
+ }
+
+ // utility - bind as required
+ private void bindAsManager(LDAPWriter w, org.opends.server.tools.LDAPReader r)
+ throws IOException, LDAPException, ASN1Exception, InterruptedException
+ {
+ bindAsWhoEver(w, r,
+ "cn=Directory Manager", "password", LDAPResultCode.SUCCESS);
+ }
+
+ // utility - bind as required
+ private void bindAsWhoEver(LDAPWriter w, org.opends.server.tools.LDAPReader r,
+ String bindDN, String password, int expected)
+ throws IOException, LDAPException, ASN1Exception, InterruptedException
+ {
+// Since we are going to be watching the post-response count, we need to
+// wait for the server to become idle before kicking off the next request to
+// ensure that any remaining post-response processing from the previous
+// operation has completed.
+ assertTrue(DirectoryServer.getWorkQueue().waitUntilIdle(10000));
+
+
+ InvocationCounterPlugin.resetAllCounters();
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(
+ ByteString.valueOf(bindDN),
+ 3, ByteString.valueOf(password));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ w.writeMessage(message);
+
+ message = r.readMessage();
+ BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
+// assertEquals(InvocationCounterPlugin.waitForPostResponse(), 1);
+ assertEquals(bindResponse.getResultCode(), expected);
+ }
+
+ /**
+ * Clean up the environment.
+ *
+ * @throws Exception If the environment could not be set up.
+ */
+ @AfterClass
+
+ public void classCleanUp() throws Exception
+ {
+ callParanoiaCheck = false;
+ super.classCleanUp();
+
+ shutdown();
+
+ paranoiaCheck();
+ }
+
+ /**
+ * After the tests stop the replicationServer.
+ */
+ protected void shutdown() throws Exception
+ {
+ if (replicationServer != null)
+ replicationServer.remove();
+ /*
+ TestCaseUtils.dsconfig(
+ "delete-replication-server",
+ "--provider-name", "Multimaster Synchronization");
+ */
+ replicationServer = null;
+ }
+ /**
+ * Utility - sleeping as long as required
+ */
+ private void sleep(long time)
+ {
+ try
+ {
+ Thread.sleep(time);
+ } catch (InterruptedException ex)
+ {
+ fail("Error sleeping " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Utility - log debug message - highlight it is from the test and not
+ * from the server code. Makes easier to observe the test steps.
+ */
+ private void debugInfo(String tn, String s)
+ {
+ //if (debugEnabled())
+ {
+ logError(Message.raw(Category.SYNC, Severity.NOTICE,
+ "** TEST " + tn + " ** " + s));
+ TRACER.debugInfo("** TEST " + tn + " ** " + s);
+ }
+ }
+
+ /**
+ * Utility - create a second backend in order to test ECL with 2 suffixes.
+ */
+ private static Backend initializeTestBackend2(boolean createBaseEntry)
+ throws IOException, InitializationException, ConfigException,
+ DirectoryException
+ {
+
+ DN baseDN = DN.decode(TEST_ROOT_DN_STRING2);
+
+ // Retrieve backend. Warning: it is important to perform this each time,
+ // because a test may have disabled then enabled the backend (i.e a test
+ // performing an import task). As it is a memory backend, when the backend
+ // is re-enabled, a new backend object is in fact created and old reference
+ // to memory backend must be invalidated. So to prevent this problem, we
+ // retrieve the memory backend reference each time before cleaning it.
+ MemoryBackend memoryBackend =
+ (MemoryBackend)DirectoryServer.getBackend(TEST_BACKEND_ID2);
+
+ if (memoryBackend == null)
+ {
+ memoryBackend = new MemoryBackend();
+ memoryBackend.setBackendID(TEST_BACKEND_ID2);
+ memoryBackend.setBaseDNs(new DN[] {baseDN});
+ memoryBackend.initializeBackend();
+ DirectoryServer.registerBackend(memoryBackend);
+ }
+
+ memoryBackend.clearMemoryBackend();
+
+ if (createBaseEntry)
+ {
+ Entry e = createEntry(baseDN);
+ memoryBackend.addEntry(e, null);
+ }
+ return memoryBackend;
+ }
+
+ private static void removeTestBackend2(Backend backend)
+ {
+ MemoryBackend memoryBackend = (MemoryBackend)backend;
+ memoryBackend.finalizeBackend();
+ DirectoryServer.deregisterBackend(memoryBackend);
+ }
+}
\ No newline at end of file
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
index 00afcce..4a2b53c 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
@@ -22,20 +22,26 @@
* CDDL HEADER END
*
*
- * Copyright 2006-2008 Sun Microsystems, Inc.
+ * Copyright 2006-2009 Sun Microsystems, Inc.
*/
package org.opends.server.replication.protocol;
-import static org.opends.server.replication.protocol.OperationContext.*;
-import static org.testng.Assert.*;
+import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
+import static org.opends.server.replication.protocol.OperationContext.SYNCHROCONTEXT;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
-import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.zip.DataFormatException;
+import org.opends.messages.Message;
import org.opends.server.core.AddOperation;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.DeleteOperationBasis;
@@ -44,8 +50,13 @@
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.AssuredMode;
import org.opends.server.replication.common.ChangeNumber;
+import org.opends.server.replication.common.DSInfo;
+import org.opends.server.replication.common.MultiDomainServerState;
+import org.opends.server.replication.common.RSInfo;
import org.opends.server.replication.common.ServerState;
+import org.opends.server.replication.common.ServerStatus;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
@@ -60,15 +71,9 @@
import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation;
import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
-import org.opends.messages.Message;
-import org.opends.server.replication.common.AssuredMode;
-import org.opends.server.replication.common.DSInfo;
-import org.opends.server.replication.common.RSInfo;
-import org.opends.server.replication.common.ServerStatus;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
-import static org.opends.server.TestCaseUtils.*;
/**
* Test the constructors, encoders and decoders of the replication protocol
@@ -563,18 +568,18 @@
throws Exception
{
AckMsg msg1, msg2 ;
-
+
// Consctructor test (with ChangeNumber)
// Chech that retrieved CN is OK
msg1 = new AckMsg(cn);
assertEquals(msg1.getChangeNumber().compareTo(cn), 0);
-
+
// Check default values for error info
assertFalse(msg1.hasTimeout());
assertFalse(msg1.hasWrongStatus());
assertFalse(msg1.hasReplayError());
assertTrue(msg1.getFailedServers().size() == 0);
-
+
// Check constructor with error info
msg1 = new AckMsg(cn, hasTimeout, hasWrongStatus, hasReplayError, failedServers);
assertEquals(msg1.getChangeNumber().compareTo(cn), 0);
@@ -582,7 +587,7 @@
assertTrue(msg1.hasWrongStatus() == hasWrongStatus);
assertTrue(msg1.hasReplayError() == hasReplayError);
assertEquals(msg1.getFailedServers(), failedServers);
-
+
// Consctructor test (with byte[])
msg2 = new AckMsg(msg1.getBytes());
assertEquals(msg2.getChangeNumber().compareTo(cn), 0);
@@ -590,7 +595,7 @@
assertTrue(msg1.hasWrongStatus() == msg2.hasWrongStatus());
assertTrue(msg1.hasReplayError() == msg2.hasReplayError());
assertEquals(msg1.getFailedServers(), msg2.getFailedServers());
-
+
// Check invalid bytes for constructor
byte[] b = msg1.getBytes();
b[0] = ReplicationMsg.MSG_TYPE_ADD;
@@ -604,11 +609,53 @@
{
assertTrue(true);
}
-
+
// Check that retrieved CN is OK
msg2 = (AckMsg) ReplicationMsg.generateMsg(msg1.getBytes());
}
+ @Test()
+ public void eclUpdateMsg()
+ throws Exception
+ {
+ // create a msg to put in the eclupdatemsg
+ InternalClientConnection connection =
+ InternalClientConnection.getRootConnection();
+ DeleteOperationBasis opBasis =
+ new DeleteOperationBasis(connection, 1, 1,null, DN.decode("cn=t1"));
+ LocalBackendDeleteOperation op = new LocalBackendDeleteOperation(opBasis);
+ ChangeNumber cn = new ChangeNumber(TimeThread.getTime(),
+ (short) 123, (short) 45);
+ op.setAttachment(SYNCHROCONTEXT, new DeleteContext(cn, "uniqueid"));
+ DeleteMsg delmsg = new DeleteMsg(op);
+
+ String serviceId = "serviceid";
+
+ // create a cookie
+ MultiDomainServerState cookie =
+ new MultiDomainServerState(
+ "o=test:000001210b6f21e904b100000001 000001210b6f21e904b200000001;" +
+ "o=test2:000001210b6f21e904b100000002 000001210b6f21e904b200000002;");
+
+ // Constructor test
+ ECLUpdateMsg msg1 = new ECLUpdateMsg(delmsg, cookie, serviceId);
+ assertTrue(msg1.getCookie().equalsTo(cookie));
+ assertTrue(msg1.getServiceId().equalsIgnoreCase(serviceId));
+ DeleteMsg delmsg2 = (DeleteMsg)msg1.getUpdateMsg();
+ assertTrue(delmsg.compareTo(delmsg2)==0);
+
+ // Consctructor test (with byte[])
+ ECLUpdateMsg msg2 = new ECLUpdateMsg(msg1.getBytes());
+ assertTrue(msg2.getCookie().equalsTo(msg2.getCookie()));
+ assertTrue(msg2.getCookie().equalsTo(cookie));
+ assertTrue(msg2.getServiceId().equalsIgnoreCase(msg1.getServiceId()));
+ assertTrue(msg2.getServiceId().equalsIgnoreCase(serviceId));
+ DeleteMsg delmsg1 = (DeleteMsg)msg1.getUpdateMsg();
+ delmsg2 = (DeleteMsg)msg2.getUpdateMsg();
+ assertTrue(delmsg2.compareTo(delmsg)==0);
+ assertTrue(delmsg2.compareTo(delmsg1)==0);
+ }
+
@DataProvider(name="createServerStartData")
public Object [][] createServerStartData() throws Exception
{
@@ -1120,4 +1167,85 @@
UpdateMsg newMsg = new UpdateMsg(msg.getBytes());
assertEquals(test.getBytes(), newMsg.getPayload());
}
+
+ /**
+ * Test that ServerStartMsg encoding and decoding works
+ * by checking that : msg == new ServerStartMsg(msg.getBytes()).
+ */
+ @Test(dataProvider="createServerStartData")
+ public void startECLMsgTest(short serverId, String baseDN, int window,
+ ServerState state, long genId, boolean sslEncryption, byte groupId) throws Exception
+ {
+ ServerStartECLMsg msg = new ServerStartECLMsg(baseDN,
+ window, window, window, window, window, window, state,
+ ProtocolVersion.getCurrentVersion(), genId, sslEncryption, groupId);
+ ServerStartECLMsg newMsg = new ServerStartECLMsg(msg.getBytes());
+ assertEquals(msg.getMaxReceiveDelay(), newMsg.getMaxReceiveDelay());
+ assertEquals(msg.getMaxReceiveQueue(), newMsg.getMaxReceiveQueue());
+ assertEquals(msg.getMaxSendDelay(), newMsg.getMaxSendDelay());
+ assertEquals(msg.getMaxSendQueue(), newMsg.getMaxSendQueue());
+ assertEquals(msg.getWindowSize(), newMsg.getWindowSize());
+ assertEquals(msg.getHeartbeatInterval(), newMsg.getHeartbeatInterval());
+ assertEquals(msg.getSSLEncryption(), newMsg.getSSLEncryption());
+ assertEquals(msg.getServerState().getMaxChangeNumber((short)1),
+ newMsg.getServerState().getMaxChangeNumber((short)1));
+ assertEquals(msg.getVersion(), newMsg.getVersion());
+ assertEquals(msg.getGenerationId(), newMsg.getGenerationId());
+ assertTrue(msg.getGroupId() == newMsg.getGroupId());
+ }
+ /**
+ * Test StartSessionMsg encoding and decoding.
+ */
+ @Test()
+ public void startECLSessionMsgTest()
+ throws Exception
+ {
+ // data
+ ChangeNumber changeNumber = new ChangeNumber(TimeThread.getTime(),
+ (short) 123, (short) 45);
+ String generalizedState = new String("fakegenstate");
+ ServerState state = new ServerState();
+ assertTrue(state.update(new ChangeNumber((long)75, 5,(short)263)));
+ short mode = 3;
+ int firstDraftChangeNumber = 13;
+ int lastDraftChangeNumber = 14;
+ String myopid = new String("fakeopid");
+ // create original
+ StartECLSessionMsg msg = new StartECLSessionMsg();
+ msg.setChangeNumber(changeNumber);
+ msg.setCrossDomainServerState(generalizedState);
+ msg.setPersistent(StartECLSessionMsg.PERSISTENT);
+ msg.setFirstDraftChangeNumber(firstDraftChangeNumber);
+ msg.setLastDraftChangeNumber(lastDraftChangeNumber);
+ msg.setECLRequestType(mode);
+ msg.setOperationId(myopid);
+ ArrayList<String> dns = new ArrayList<String>();
+ String dn1 = "cn=admin data";
+ String dn2 = "cn=config";
+ dns.add(dn1);
+ dns.add(dn2);
+ msg.setExcludedDNs(dns);
+ // create copy
+ StartECLSessionMsg newMsg = new StartECLSessionMsg(msg.getBytes());
+ // test equality between the two copies
+ assertEquals(msg.getChangeNumber(), newMsg.getChangeNumber());
+ assertTrue(msg.isPersistent() == newMsg.isPersistent());
+ assertTrue(msg.getFirstDraftChangeNumber() == newMsg.getFirstDraftChangeNumber());
+ assertEquals(msg.getECLRequestType(), newMsg.getECLRequestType());
+ assertEquals(msg.getLastDraftChangeNumber(), newMsg.getLastDraftChangeNumber());
+ assertTrue(
+ msg.getCrossDomainServerState().equalsIgnoreCase(newMsg.getCrossDomainServerState()));
+ assertTrue(
+ msg.getOperationId().equalsIgnoreCase(newMsg.getOperationId()));
+ ArrayList<String> dns2 = newMsg.getExcludedServiceIDs();
+ assertTrue(dns2.size()==2);
+ boolean dn1found=false,dn2found=false;
+ for (String dn : dns2)
+ {
+ if (!dn1found) dn1found=(dn.compareTo(dn1)==0);
+ if (!dn2found) dn2found=(dn.compareTo(dn2)==0);
+ }
+ assertTrue(dn1found);
+ assertTrue(dn2found);
+ }
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
index 89bce13..eeb893f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/server/ReplicationServerTest.java
@@ -90,6 +90,8 @@
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.TimeThread;
import static org.opends.server.util.ServerConstants.OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
import org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -175,7 +177,7 @@
private void debugInfo(String s)
{
- ErrorLogger.logError(Message.raw(Category.SYNC, Severity.NOTICE, "** TEST ** " + s));
+ //ErrorLogger.logError(Message.raw(Category.SYNC, Severity.NOTICE, "** TEST ** " + s));
if (debugEnabled())
{
TRACER.debugInfo("** TEST ** " + s);
@@ -689,7 +691,7 @@
new ChangeNumberGenerator(serverId , (long) 0);
broker[i] =
openReplicationSession( DN.decode(TEST_ROOT_DN_STRING), serverId,
- 100, replicationServerPort, 100000, 1000, 0, false);
+ 100, replicationServerPort, 3000, 1000, 0, true);
assertTrue(broker[i].isConnected());
@@ -706,10 +708,11 @@
{
reader[i].start();
}
- debugInfo("Ending multipleWriterMultipleReader");
+ debugInfo("multipleWriterMultipleReader produces and readers started");
}
finally
{
+ debugInfo("multipleWriterMultipleReader wait producers");
for (int i = 0; i< THREADS; i++)
{
if (producer[i] != null)
@@ -717,6 +720,7 @@
// kill the thread in case it is not yet stopped.
producer[i].interrupt();
}
+ debugInfo("multipleWriterMultipleReader producers done, wait readers");
for (int i = 0; i< THREADS; i++)
{
if (reader[i] != null)
@@ -724,15 +728,18 @@
// kill the thread in case it is not yet stopped.
reader[i].interrupt();
}
+ debugInfo("multipleWriterMultipleReader reader's done");
for (int i = 0; i< THREADS; i++)
{
if (broker[i] != null)
broker[i].stop();
}
+ debugInfo("multipleWriterMultipleReader brokers stopped");
replicationServer.clearDb();
TestCaseUtils.initializeTestBackend(true);
}
+ debugInfo("Ending multipleWriterMultipleReader");
}
@@ -1123,6 +1130,8 @@
*/
private class BrokerReader extends Thread
{
+ int count;
+
private ReplicationBroker broker;
private int numMsgRcv = 0;
private final int numMsgExpected;
@@ -1142,8 +1151,11 @@
@Override
public void run()
{
+ debugInfo("BrokerReader " + broker.getServerId() + " starts");
+
// loop receiving messages until either we get a timeout
// because there is nothing left or an error condition happens.
+ count = 0;
try
{
while (true)
@@ -1165,7 +1177,8 @@
} catch (Exception e)
{
assertTrue(false,
- "a BrokerReader received an Exception" + e.getMessage());
+ "a BrokerReader received an Exception" + e.getMessage()
+ + stackTraceToSingleLineString(e));
}
}
}
@@ -1194,6 +1207,8 @@
@Override
public void run()
{
+ debugInfo("BrokerWriter " + broker.getServerId() + " starts");
+ int ccount = count;
/*
* Simple loop creating changes and sending them
* to the replicationServer.
@@ -1206,7 +1221,11 @@
new DeleteMsg("o=example," + TEST_ROOT_DN_STRING, gen.newChangeNumber(),
"uid");
broker.publish(msg);
+
+ if ((count % 10) == 0)
+ debugInfo("BrokerWriter " + broker.getServerId() + " sent="+count);
}
+ debugInfo("BrokerWriter " + broker.getServerId() + " ends sent="+ccount);
}
}
--
Gitblit v1.10.0