Merge the replication-service branch with the OpenDS trunk
This commit includes all the changes for :
- issue 3640 : Refactor Replication code to make it more generic
- issue 497 : Assured replication
3 files deleted
12 files added
9 files renamed
67 files modified
| | |
| | | ds-cfg-allowed-task: org.opends.server.tasks.EnterLockdownModeTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.ExportTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.ImportTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.InitializeTargetTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.InitializeTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.SetGenerationIdTask |
| | | ds-cfg-allowed-task: org.opends.server.replication.service.InitializeTargetTask |
| | | ds-cfg-allowed-task: org.opends.server.replication.service.InitializeTask |
| | | ds-cfg-allowed-task: org.opends.server.replication.service.SetGenerationIdTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.LeaveLockdownModeTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.RebuildTask |
| | | ds-cfg-allowed-task: org.opends.server.tasks.RestoreTask |
| | |
| | | connection |
| | | SEVERE_ERR_WRITER_UNEXPECTED_EXCEPTION_32=An unexpected error happened \ |
| | | handling connection with %s. This connection is going to be closed |
| | | SEVERE_ERR_CHANGELOG_ERROR_SENDING_ACK_33=An unexpected error occurred while \ |
| | | sending an ack to %s. This connection is going to be closed and reopened |
| | | SEVERE_ERR_RS_ERROR_SENDING_ACK_33=In replication server %s: an unexpected error \ |
| | | occurred while sending an ack to server id %s for change number %s in domain %s \ |
| | | . This connection is going to be closed and reopened |
| | | SEVERE_ERR_EXCEPTION_RECEIVING_REPLICATION_MESSAGE_34=An Exception was caught \ |
| | | while receiving replication message : %s |
| | | MILD_ERR_LOOP_REPLAYING_OPERATION_35=A loop was detected while replaying \ |
| | |
| | | DEBUG_SENDING_CHANGE_112=Sending change number: %s |
| | | DEBUG_CHANGES_SENT_113=All missing changes sent to replication server |
| | | SEVERE_ERR_PUBLISHING_FAKE_OPS_114=Caught exception publishing fake operations \ |
| | | for domain %s to replication server %s : %s |
| | | for domain %s : %s |
| | | SEVERE_ERR_COMPUTING_FAKE_OPS_115=Caught exception computing fake operations \ |
| | | for domain %s for replication server %s : %s |
| | | NOTICE_IGNORING_REMOTE_MONITOR_DATA_116=Some monitor data have been received \ |
| | |
| | | server %s will be aborted. Simultanate cross connection attempt ? |
| | | NOTICE_BAD_GENERATION_ID_FROM_DS_146=On suffix %s, directory server %s presented \ |
| | | generation ID=%s when expected generation ID=%s |
| | | SEVERE_ERR_DS_RECEIVED_ACK_ERROR_147=In replication service %s, the assured \ |
| | | update message %s was acknowledged with the following errors: %s |
| | | SEVERE_ERR_DS_ACK_TIMEOUT_148=In replication service %s, timeout after %s ms \ |
| | | waiting for the acknowledgement of the assured update message: %s |
| | | SEVERE_ERR_DS_UNKNOWN_ASSURED_MODE_149=In directory server %s, received unknown \ |
| | | assured update mode: %s, for domain %s. Message: %s |
| | | SEVERE_ERR_RS_UNKNOWN_ASSURED_MODE_150=In replication server %s, received unknown \ |
| | | assured update mode: %s, for domain %s. Message: %s |
| | | SEVERE_ERR_UNKNOWN_ASSURED_SAFE_DATA_LEVEL_151=In replication server %s, \ |
| | | received a safe data assured update message with incoherent level: %s, this is \ |
| | | for domain %s. Message: %s |
| | | |
| | |
| | | oc.add("ds-task"); |
| | | oc.add("ds-task-initialize-from-remote-replica"); |
| | | attrs.put(oc); |
| | | attrs.put("ds-task-class-name", "org.opends.server.tasks.InitializeTask"); |
| | | attrs.put( |
| | | "ds-task-class-name", |
| | | "org.opends.server.replication.service.InitializeTask"); |
| | | attrs.put("ds-task-initialize-domain-dn", suffixDn); |
| | | attrs.put("ds-task-initialize-replica-server-id", |
| | | String.valueOf(replicaId)); |
| | |
| | | oc.add("ds-task-reset-generation-id"); |
| | | attrs.put(oc); |
| | | attrs.put("ds-task-class-name", |
| | | "org.opends.server.tasks.SetGenerationIdTask"); |
| | | "org.opends.server.replication.service.SetGenerationIdTask"); |
| | | attrs.put("ds-task-reset-generation-id-domain-base-dn", suffixDn); |
| | | while (!taskCreated) |
| | | { |
| | |
| | | public class ServerState implements Iterable<Short> |
| | | { |
| | | private HashMap<Short, ChangeNumber> list; |
| | | private boolean saved = true; |
| | | |
| | | /** |
| | | * Creates a new empty ServerState. |
| | |
| | | |
| | | /** |
| | | * Update the Server State with a ChangeNumber. |
| | | * All operations with smaller CSN and the same serverID must be committed |
| | | * before calling this method. |
| | | * @param changeNumber the committed ChangeNumber. |
| | | * @return a boolean indicating if the update was meaningfull. |
| | | * |
| | | * @param changeNumber The committed ChangeNumber. |
| | | * |
| | | * @return a boolean indicating if the update was meaningful. |
| | | */ |
| | | public boolean update(ChangeNumber changeNumber) |
| | | { |
| | | if (changeNumber == null) |
| | | return false; |
| | | |
| | | saved = false; |
| | | |
| | | synchronized(this) |
| | | { |
| | | Short id = changeNumber.getServerId(); |
| | |
| | | |
| | | return diff; |
| | | } |
| | | |
| | | /** |
| | | * Set the saved status of this ServerState. |
| | | * |
| | | * @param b A booelan indicating if the State has been safely stored. |
| | | */ |
| | | public void setSaved(boolean b) |
| | | { |
| | | saved = false; |
| | | } |
| | | |
| | | /** |
| | | * Get the saved status of this ServerState. |
| | | * |
| | | * @return The saved status of this ServerState. |
| | | */ |
| | | public boolean isSaved() |
| | | { |
| | | return saved; |
| | | } |
| | | } |
| | |
| | | { |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * Get a user readable string representing this status (User friendly string |
| | | * for monitoring purpose). |
| | | * @return A user readable string representing this status. |
| | | */ |
| | | public String toString() |
| | | { |
| | | switch (value) |
| | | { |
| | | case -1: |
| | | return "Invalid"; |
| | | case 0: |
| | | return "Not connected"; |
| | | case 1: |
| | | return "Normal"; |
| | | case 2: |
| | | return "Degraded"; |
| | | case 3: |
| | | return "Full update"; |
| | | case 4: |
| | | return "Bad generation id"; |
| | | default: |
| | | throw new IllegalArgumentException("Wrong status numeric value: " + |
| | | value); |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | return new AddMsg(getChangeNumber(), entry.getDN().toString(), |
| | | Historical.getEntryUuid(entry), |
| | | ReplicationDomain.findEntryId( |
| | | LDAPReplicationDomain.findEntryId( |
| | | entry.getDN().getParentDNInSuffix()), |
| | | entry.getObjectClasses(), |
| | | entry.getUserAttributes(), entry.getOperationalAttributes()); |
| | |
| | | DN dn = entry.getDN(); |
| | | return new ModifyDNMsg(dn.toString(), this.getChangeNumber(), |
| | | Historical.getEntryUuid(entry), |
| | | ReplicationDomain.findEntryId(dn.getParent()), |
| | | LDAPReplicationDomain.findEntryId(dn.getParent()), |
| | | false, dn.getParent().toString(), dn.getRDN().toString()); |
| | | } |
| | | } |
| File was renamed from opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java |
| | |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | import static org.opends.messages.ToolMessages.*; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | |
| | | import static org.opends.server.util.StaticUtils.createEntry; |
| | | import static org.opends.server.util.StaticUtils.getFileForPath; |
| | | import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; |
| | | import static org.opends.server.replication.common.StatusMachine.*; |
| | | |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | |
| | | import org.opends.server.replication.service.ReplicationMonitor; |
| | | |
| | | import java.util.Collection; |
| | | |
| | | import org.opends.server.types.Attributes; |
| | | |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import java.net.SocketTimeoutException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.SortedMap; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeMap; |
| | | import java.util.Set; |
| | | import java.util.TreeSet; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | import java.util.zip.CheckedOutputStream; |
| | | import java.util.zip.DataFormatException; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.messages.MessageBuilder; |
| | | import org.opends.server.admin.server.ConfigurationChangeListener; |
| | |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.api.SynchronizationProvider; |
| | | import org.opends.server.backends.jeb.BackendImpl; |
| | | import org.opends.server.backends.task.Task; |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.core.AddOperation; |
| | | import org.opends.server.core.DeleteOperation; |
| | |
| | | import org.opends.server.protocols.asn1.ASN1Exception; |
| | | import org.opends.server.protocols.asn1.ASN1OctetString; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchListener; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPAttribute; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | import org.opends.server.protocols.ldap.LDAPModification; |
| | | import org.opends.server.replication.service.ReplicationDomain; |
| | | import org.opends.server.replication.common.AssuredMode; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | 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.AckMsg; |
| | | import org.opends.server.replication.protocol.AddContext; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.ChangeStatusMsg; |
| | | import org.opends.server.replication.protocol.DeleteContext; |
| | | import org.opends.server.replication.protocol.DoneMsg; |
| | | import org.opends.server.replication.protocol.EntryMsg; |
| | | import org.opends.server.replication.protocol.ErrorMsg; |
| | | import org.opends.server.replication.protocol.HeartbeatMsg; |
| | | import org.opends.server.replication.protocol.InitializeRequestMsg; |
| | | import org.opends.server.replication.protocol.InitializeTargetMsg; |
| | | import org.opends.server.replication.protocol.ModifyContext; |
| | | 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.OperationContext; |
| | | import org.opends.server.replication.protocol.ReplSessionSecurity; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | 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.ProtocolSession; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.tasks.InitializeTargetTask; |
| | | import org.opends.server.tasks.InitializeTask; |
| | | import org.opends.server.tasks.TaskUtils; |
| | | import org.opends.server.types.AttributeBuilder; |
| | | import org.opends.server.types.Attributes; |
| | | import org.opends.server.types.ExistingFileBehavior; |
| | | import org.opends.server.types.AbstractOperation; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeBuilder; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.ConfigChangeResult; |
| | |
| | | import org.opends.server.types.DereferencePolicy; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.ExistingFileBehavior; |
| | | import org.opends.server.types.LDAPException; |
| | | import org.opends.server.types.LDIFExportConfig; |
| | | import org.opends.server.types.LDIFImportConfig; |
| | |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchResultReference; |
| | | import org.opends.server.types.SearchScope; |
| | | import org.opends.server.types.SynchronizationProviderResult; |
| | | import org.opends.server.types.operation.PluginOperation; |
| | |
| | | * handle conflict resolution, |
| | | * handle protocol messages from the replicationServer. |
| | | */ |
| | | public class ReplicationDomain extends DirectoryThread |
| | | public class LDAPReplicationDomain extends ReplicationDomain |
| | | implements ConfigurationChangeListener<ReplicationDomainCfg>, |
| | | AlertGenerator |
| | | AlertGenerator, InternalSearchListener |
| | | { |
| | | /** |
| | | * The fully-qualified name of this class. |
| | |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | private ReplicationMonitor monitor; |
| | | |
| | | private ReplicationBroker broker; |
| | | // Thread waiting for incoming update messages for this domain and pushing |
| | | // them to the global incoming update message queue for later processing by |
| | | // replay threads. |
| | | private ListenerThread listenerThread; |
| | | // The update to replay message queue where the listener thread is going to |
| | | // push incoming update messages. |
| | | private LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue; |
| | | private SortedMap<ChangeNumber, UpdateMsg> waitingAckMsgs = |
| | | new TreeMap<ChangeNumber, UpdateMsg>(); |
| | | private AtomicInteger numRcvdUpdates = new AtomicInteger(0); |
| | | private AtomicInteger numSentUpdates = new AtomicInteger(0); |
| | | private AtomicInteger numProcessedUpdates = new AtomicInteger(); |
| | | private AtomicInteger numResolvedNamingConflicts = new AtomicInteger(); |
| | | private AtomicInteger numResolvedModifyConflicts = new AtomicInteger(); |
| | | private AtomicInteger numUnresolvedNamingConflicts = new AtomicInteger(); |
| | | private int debugCount = 0; |
| | | private PersistentServerState state; |
| | | private final PersistentServerState state; |
| | | private int numReplayedPostOpCalled = 0; |
| | | |
| | | private int maxReceiveQueue = 0; |
| | | private int maxSendQueue = 0; |
| | | private int maxReceiveDelay = 0; |
| | | private int maxSendDelay = 0; |
| | | |
| | | private long generationId = -1; |
| | | private boolean generationIdSavedStatus = false; |
| | | |
| | | ChangeNumberGenerator generator; |
| | | private ChangeNumberGenerator generator; |
| | | |
| | | /** |
| | | * This object is used to store the list of update currently being |
| | |
| | | */ |
| | | private RemotePendingChanges remotePendingChanges; |
| | | |
| | | /** |
| | | * The time in milliseconds between heartbeats from the replication |
| | | * server. Zero means heartbeats are off. |
| | | */ |
| | | private long heartbeatInterval = 0; |
| | | private short serverId; |
| | | |
| | | // The context related to an import or export being processed |
| | | // Null when none is being processed. |
| | | private IEContext ieContext = null; |
| | | |
| | | private Collection<String> replicationServers; |
| | | |
| | | private DN baseDn; |
| | | |
| | | private boolean shutdown = false; |
| | |
| | | private boolean disabled = false; |
| | | private boolean stateSavingDisabled = false; |
| | | |
| | | private int window = 100; |
| | | |
| | | /* |
| | | * Assured mode properties |
| | | */ |
| | | // Is assured mode enabled or not for this domain ? |
| | | private boolean assured = false; |
| | | // Assured sub mode (used when assured is true) |
| | | private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE; |
| | | // Safe Data level (used when assuredMode is SAFE_DATA) |
| | | private byte assuredSdLevel = (byte)1; |
| | | // Timeout (in milliseconds) when waiting for acknowledgments |
| | | private long assuredTimeout = 1000; |
| | | |
| | | // Group id |
| | | private byte groupId = (byte)1; |
| | | // Referrals urls to be published to other servers of the topology |
| | | // TODO: fill that with all currently opened urls if no urls configured |
| | | private List<String> refUrls = new ArrayList<String>(); |
| | | |
| | | // Current status for this replicated domain |
| | | private ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS; |
| | | |
| | | /* |
| | | * Properties for the last topology info received from the network. |
| | | */ |
| | | // Info for other DSs. |
| | | // Warning: does not contain info for us (for our server id) |
| | | private List<DSInfo> dsList = new ArrayList<DSInfo>(); |
| | | // Info for other RSs. |
| | | private List<RSInfo> rsList = new ArrayList<RSInfo>(); |
| | | // This list is used to temporary store operations that needs |
| | | // to be replayed at session establishment time. |
| | | private TreeSet<FakeOperation> replayOperations = |
| | | new TreeSet<FakeOperation>(new FakeOperationComparator());; |
| | | |
| | | /** |
| | | * The isolation policy that this domain is going to use. |
| | |
| | | */ |
| | | private boolean done = true; |
| | | |
| | | private ServerStateFlush flushThread; |
| | | |
| | | /** |
| | | * This class contain the context related to an import or export |
| | | * launched on the domain. |
| | | * The thread that periodically saves the ServerState of this |
| | | * LDAPReplicationDomain in the database. |
| | | */ |
| | | private class IEContext |
| | | private class ServerStateFlush extends DirectoryThread |
| | | { |
| | | // The task that initiated the operation. |
| | | Task initializeTask; |
| | | // The input stream for the import |
| | | ReplLDIFInputStream ldifImportInputStream = null; |
| | | // The target in the case of an export |
| | | short exportTarget = RoutableMsg.UNKNOWN_SERVER; |
| | | // The source in the case of an import |
| | | short importSource = RoutableMsg.UNKNOWN_SERVER; |
| | | |
| | | // The total entry count expected to be processed |
| | | long entryCount = 0; |
| | | // The count for the entry not yet processed |
| | | long entryLeftCount = 0; |
| | | |
| | | // The exception raised when any |
| | | DirectoryException exception = null; |
| | | |
| | | /** |
| | | * Initializes the import/export counters with the provider value. |
| | | * @param total |
| | | * @param left |
| | | * @throws DirectoryException |
| | | */ |
| | | public void setCounters(long total, long left) |
| | | throws DirectoryException |
| | | protected ServerStateFlush() |
| | | { |
| | | entryCount = total; |
| | | entryLeftCount = left; |
| | | |
| | | if (initializeTask != null) |
| | | { |
| | | if (initializeTask instanceof InitializeTask) |
| | | { |
| | | ((InitializeTask)initializeTask).setTotal(entryCount); |
| | | ((InitializeTask)initializeTask).setLeft(entryCount); |
| | | } |
| | | else if (initializeTask instanceof InitializeTargetTask) |
| | | { |
| | | ((InitializeTargetTask)initializeTask).setTotal(entryCount); |
| | | ((InitializeTargetTask)initializeTask).setLeft(entryCount); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update the counters of the task for each entry processed during |
| | | * an import or export. |
| | | * @throws DirectoryException |
| | | */ |
| | | public void updateCounters() |
| | | throws DirectoryException |
| | | { |
| | | entryLeftCount--; |
| | | |
| | | if (initializeTask != null) |
| | | { |
| | | if (initializeTask instanceof InitializeTask) |
| | | { |
| | | ((InitializeTask)initializeTask).setLeft(entryLeftCount); |
| | | } |
| | | else if (initializeTask instanceof InitializeTargetTask) |
| | | { |
| | | ((InitializeTargetTask)initializeTask).setLeft(entryLeftCount); |
| | | } |
| | | } |
| | | super("Replication State Saver for server id " + |
| | | serverId + " and domain " + baseDn.toString()); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public String toString() |
| | | { |
| | | return new String("[ Entry count=" + this.entryCount + |
| | | ", Entry left count=" + this.entryLeftCount + "]"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * This thread is launched when we want to export data to another server that |
| | | * has requested to be initialized with the data of our backend. |
| | | */ |
| | | private class ExportThread extends DirectoryThread |
| | | { |
| | | // Id of server that will receive updates |
| | | private short target; |
| | | |
| | | /** |
| | | * Constructor for the ExportThread. |
| | | * |
| | | * @param target Id of server that will receive updates |
| | | */ |
| | | public ExportThread(short target) |
| | | { |
| | | super("Export thread " + serverId); |
| | | this.target = target; |
| | | } |
| | | |
| | | /** |
| | | * Run method for this class. |
| | | */ |
| | | @Override |
| | | public void run() |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("Export thread starting."); |
| | | } |
| | | done = false; |
| | | |
| | | try |
| | | while (shutdown == false) |
| | | { |
| | | initializeRemote(target, target, null); |
| | | } catch (DirectoryException de) |
| | | { |
| | | // An error message has been sent to the peer |
| | | // Nothing more to do locally |
| | | try |
| | | { |
| | | synchronized (this) |
| | | { |
| | | this.wait(1000); |
| | | if (!disabled && !stateSavingDisabled ) |
| | | { |
| | | // save the ServerState |
| | | state.save(); |
| | | } |
| | | } |
| | | } catch (InterruptedException e) |
| | | { } |
| | | } |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("Export thread stopping."); |
| | | } |
| | | state.save(); |
| | | |
| | | done = true; |
| | | } |
| | | } |
| | | |
| | |
| | | * @param updateToReplayQueue The queue for update messages to replay. |
| | | * @throws ConfigException In case of invalid configuration. |
| | | */ |
| | | public ReplicationDomain(ReplicationDomainCfg configuration, |
| | | public LDAPReplicationDomain(ReplicationDomainCfg configuration, |
| | | LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue) |
| | | throws ConfigException |
| | | { |
| | | super("Replication State Saver for server id " + configuration.getServerId() |
| | | + " and domain " + configuration.getBaseDN()); |
| | | super(configuration.getBaseDN().toNormalizedString(), |
| | | (short) configuration.getServerId()); |
| | | |
| | | /** |
| | | * The time in milliseconds between heartbeats from the replication |
| | | * server. Zero means heartbeats are off. |
| | | */ |
| | | long heartbeatInterval = 0; |
| | | |
| | | // Read the configuration parameters. |
| | | replicationServers = configuration.getReplicationServer(); |
| | | Set<String> replicationServers = configuration.getReplicationServer(); |
| | | serverId = (short) configuration.getServerId(); |
| | | baseDn = configuration.getBaseDN(); |
| | | window = configuration.getWindowSize(); |
| | | int window = configuration.getWindowSize(); |
| | | heartbeatInterval = configuration.getHeartbeatInterval(); |
| | | isolationpolicy = configuration.getIsolationPolicy(); |
| | | configDn = configuration.dn(); |
| | |
| | | switch (assuredType) |
| | | { |
| | | case NOT_ASSURED: |
| | | assured = false; |
| | | setAssured(false); |
| | | break; |
| | | case SAFE_DATA: |
| | | assured = true; |
| | | this.assuredMode = AssuredMode.SAFE_DATA_MODE; |
| | | setAssured(true); |
| | | setAssuredMode(AssuredMode.SAFE_DATA_MODE); |
| | | break; |
| | | case SAFE_READ: |
| | | assured = true; |
| | | this.assuredMode = AssuredMode.SAFE_READ_MODE; |
| | | setAssured(true); |
| | | setAssuredMode(AssuredMode.SAFE_READ_MODE); |
| | | break; |
| | | } |
| | | this.assuredSdLevel = (byte)configuration.getAssuredSdLevel(); |
| | | this.groupId = (byte)configuration.getGroupId(); |
| | | this.assuredTimeout = configuration.getAssuredTimeout(); |
| | | SortedSet<String> urls = configuration.getReferralsUrl(); |
| | | if (urls != null) |
| | | { |
| | | for (String url : urls) |
| | | { |
| | | this.refUrls.add(url); |
| | | } |
| | | } |
| | | setAssuredSdLevel((byte)configuration.getAssuredSdLevel()); |
| | | setAssuredTimeout(configuration.getAssuredTimeout()); |
| | | |
| | | setGroupId((byte)configuration.getGroupId()); |
| | | setURLs(configuration.getReferralsUrl()); |
| | | |
| | | /* |
| | | * Modify conflicts are solved for all suffixes but the schema suffix |
| | |
| | | solveConflictFlag = true; |
| | | } |
| | | |
| | | /* |
| | | * Create a new Persistent Server State that will be used to store |
| | | * the last ChangeNmber seen from all LDAP servers in the topology. |
| | | */ |
| | | state = new PersistentServerState(baseDn, serverId); |
| | | |
| | | /* |
| | | * Create a replication monitor object responsible for publishing |
| | | * monitoring information below cn=monitor. |
| | | */ |
| | | monitor = new ReplicationMonitor(this); |
| | | DirectoryServer.registerMonitorProvider(monitor); |
| | | |
| | | Backend backend = retrievesBackend(baseDn); |
| | | if (backend == null) |
| | | { |
| | |
| | | } |
| | | |
| | | /* |
| | | * create the broker object used to publish and receive changes |
| | | * Create a new Persistent Server State that will be used to store |
| | | * the last ChangeNmber seen from all LDAP servers in the topology. |
| | | */ |
| | | broker = new ReplicationBroker(this, state, baseDn, serverId, |
| | | maxReceiveQueue, maxReceiveDelay, maxSendQueue, maxSendDelay, window, |
| | | heartbeatInterval, generationId, |
| | | new ReplSessionSecurity(configuration),getGroupId()); |
| | | state = new PersistentServerState(baseDn, serverId, getServerState()); |
| | | |
| | | broker.start(replicationServers); |
| | | startPublishService(replicationServers, window, heartbeatInterval); |
| | | |
| | | /* |
| | | * ChangeNumberGenerator is used to create new unique ChangeNumbers |
| | |
| | | * The generator time is adjusted to the time of the last CN received from |
| | | * remote other servers. |
| | | */ |
| | | generator = |
| | | new ChangeNumberGenerator(serverId, state); |
| | | generator = getGenerator(); |
| | | |
| | | pendingChanges = |
| | | new PendingChanges(generator, |
| | | broker, state); |
| | | new PendingChanges(generator, this); |
| | | |
| | | remotePendingChanges = new RemotePendingChanges(generator, state); |
| | | remotePendingChanges = new RemotePendingChanges(getServerState()); |
| | | |
| | | // listen for changes on the configuration |
| | | configuration.addChangeListener(this); |
| | |
| | | DirectoryServer.registerAlertGenerator(this); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the base DN of this ReplicationDomain. |
| | | * |
| | |
| | | { |
| | | // this isolation policy specifies that the updates are denied |
| | | // when the broker is not connected. |
| | | return broker.isConnected(); |
| | | return isConnected(); |
| | | } |
| | | // we should never get there as the only possible policies are |
| | | // ACCEPT_ALL_UPDATES and REJECT_ALL_UPDATES |
| | |
| | | } |
| | | |
| | | /** |
| | | * Receives an update message from the replicationServer. |
| | | * also responsible for updating the list of pending changes |
| | | * @return the received message - null if none |
| | | */ |
| | | public UpdateMsg receive() |
| | | { |
| | | UpdateMsg update = null; |
| | | |
| | | while ( (update == null) && (!shutdown) ) |
| | | { |
| | | InitializeRequestMsg initMsg = null; |
| | | ReplicationMsg msg; |
| | | try |
| | | { |
| | | msg = broker.receive(); |
| | | if (msg == null) |
| | | { |
| | | // The server is in the shutdown process |
| | | return null; |
| | | } |
| | | |
| | | if (debugEnabled()) |
| | | if (!(msg instanceof HeartbeatMsg)) |
| | | TRACER.debugVerbose("Message received <" + msg + ">"); |
| | | |
| | | if (msg instanceof AckMsg) |
| | | { |
| | | AckMsg ack = (AckMsg) msg; |
| | | receiveAck(ack); |
| | | } |
| | | else if (msg instanceof InitializeRequestMsg) |
| | | { |
| | | // Another server requests us to provide entries |
| | | // for a total update |
| | | initMsg = (InitializeRequestMsg)msg; |
| | | } |
| | | else if (msg instanceof InitializeTargetMsg) |
| | | { |
| | | // Another server is exporting its entries to us |
| | | InitializeTargetMsg importMsg = (InitializeTargetMsg) msg; |
| | | |
| | | try |
| | | { |
| | | // This must be done while we are still holding the |
| | | // broker lock because we are now going to receive a |
| | | // bunch of entries from the remote server and we |
| | | // want the import thread to catch them and |
| | | // not the ListenerThread. |
| | | initialize(importMsg); |
| | | } |
| | | catch(DirectoryException de) |
| | | { |
| | | // Returns an error message to notify the sender |
| | | ErrorMsg errorMsg = |
| | | new ErrorMsg(importMsg.getsenderID(), |
| | | de.getMessageObject()); |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(de.getMessageObject()); |
| | | TRACER.debugInfo(Message.toString(mb.toMessage())); |
| | | broker.publish(errorMsg); |
| | | } |
| | | } |
| | | else if (msg instanceof ErrorMsg) |
| | | { |
| | | if (ieContext != null) |
| | | { |
| | | // This is an error termination for the 2 following cases : |
| | | // - either during an export |
| | | // - or before an import really started |
| | | // For example, when we publish a request and the |
| | | // replicationServer did not find any import source. |
| | | abandonImportExport((ErrorMsg)msg); |
| | | } |
| | | else |
| | | { |
| | | /* |
| | | * Log error message |
| | | */ |
| | | ErrorMsg errorMsg = (ErrorMsg)msg; |
| | | logError(ERR_ERROR_MSG_RECEIVED.get( |
| | | errorMsg.getDetails())); |
| | | } |
| | | } |
| | | if (msg instanceof TopologyMsg) |
| | | { |
| | | TopologyMsg topoMsg = (TopologyMsg)msg; |
| | | receiveTopo(topoMsg); |
| | | } |
| | | if (msg instanceof ChangeStatusMsg) |
| | | { |
| | | ChangeStatusMsg csMsg = (ChangeStatusMsg)msg; |
| | | receiveChangeStatus(csMsg); |
| | | } |
| | | else if (msg instanceof UpdateMsg) |
| | | { |
| | | update = (UpdateMsg) msg; |
| | | receiveUpdate(update); |
| | | } |
| | | } |
| | | catch (SocketTimeoutException e) |
| | | { |
| | | // just retry |
| | | } |
| | | // Test if we have received and export request message and |
| | | // if that's the case handle it now. |
| | | // This must be done outside of the portion of code protected |
| | | // by the broker lock so that we keep receiveing update |
| | | // when we are doing and export and so that a possible |
| | | // closure of the socket happening when we are publishing the |
| | | // entries to the remote can be handled by the other |
| | | // replay thread when they call this method and therefore the |
| | | // broker.receive() method. |
| | | if (initMsg != null) |
| | | { |
| | | // Do this work in a thread to allow replay thread continue working |
| | | ExportThread exportThread = new ExportThread(initMsg.getsenderID()); |
| | | exportThread.start(); |
| | | } |
| | | } |
| | | return update; |
| | | } |
| | | |
| | | /** |
| | | * Processes an incoming TopologyMsg. |
| | | * Updates the structures for the local view of the topology. |
| | | * |
| | | * @param topoMsg The topology information received from RS. |
| | | */ |
| | | public void receiveTopo(TopologyMsg topoMsg) |
| | | { |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + baseDn |
| | | + " received topology info update:\n" + topoMsg); |
| | | |
| | | // Store new lists |
| | | synchronized(getDsList()) |
| | | { |
| | | synchronized(getRsList()) |
| | | { |
| | | dsList = topoMsg.getDsList(); |
| | | rsList = topoMsg.getRsList(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Set the initial status of the domain, once he is connected to the topology. |
| | | * @param initStatus The status to enter the state machine with |
| | | */ |
| | | public void setInitialStatus(ServerStatus initStatus) |
| | | { |
| | | // Sanity check: is it a valid initial status? |
| | | if (!isValidInitialStatus(initStatus)) |
| | | { |
| | | Message msg = ERR_DS_INVALID_INIT_STATUS.get(initStatus.toString(), |
| | | baseDn.toString(), Short.toString(serverId)); |
| | | logError(msg); |
| | | } else |
| | | { |
| | | status = initStatus; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Processes an incoming ChangeStatusMsg. Compute new status according to |
| | | * given order. Then update domain for being compliant with new status |
| | | * definition. |
| | | * @param csMsg The received status message |
| | | */ |
| | | private void receiveChangeStatus(ChangeStatusMsg csMsg) |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + baseDn + |
| | | " received change status message:\n" + csMsg); |
| | | |
| | | ServerStatus reqStatus = csMsg.getRequestedStatus(); |
| | | |
| | | // Translate requested status to a state machine event |
| | | StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus); |
| | | if (event == StatusMachineEvent.INVALID_EVENT) |
| | | { |
| | | Message msg = ERR_DS_INVALID_REQUESTED_STATUS.get(reqStatus.toString(), |
| | | baseDn.toString(), Short.toString(serverId)); |
| | | logError(msg); |
| | | return; |
| | | } |
| | | |
| | | // Compute new status and do matching tasks |
| | | // Use synchronized as admin task (thread) could order to go in admin status |
| | | // for instance (concurrent with receive thread). |
| | | synchronized (status) |
| | | { |
| | | ServerStatus newStatus = |
| | | StatusMachine.computeNewStatus(status, event); |
| | | |
| | | if (newStatus == ServerStatus.INVALID_STATUS) |
| | | { |
| | | Message msg = ERR_DS_CANNOT_CHANGE_STATUS.get(baseDn.toString(), |
| | | Short.toString(serverId), status.toString(), event.toString()); |
| | | logError(msg); |
| | | return; |
| | | } |
| | | |
| | | // Store new status |
| | | status = newStatus; |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + baseDn + |
| | | " new status is: " + status); |
| | | |
| | | // Perform whatever actions are needed to apply properties for being |
| | | // compliant with new status |
| | | updateDomainForNewStatus(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Called when first connection or disconnection detected. |
| | | */ |
| | | public void toNotConnectedStatus() |
| | | { |
| | | // Go into not connected status |
| | | // Use synchronized as somebody could ask another status change at the same |
| | | // time |
| | | synchronized (status) |
| | | { |
| | | StatusMachineEvent event = |
| | | StatusMachineEvent.TO_NOT_CONNECTED_STATUS_EVENT; |
| | | ServerStatus newStatus = |
| | | StatusMachine.computeNewStatus(status, event); |
| | | |
| | | if (newStatus == ServerStatus.INVALID_STATUS) |
| | | { |
| | | Message msg = ERR_DS_CANNOT_CHANGE_STATUS.get(baseDn.toString(), |
| | | Short.toString(serverId), status.toString(), event.toString()); |
| | | logError(msg); |
| | | return; |
| | | } |
| | | |
| | | // Store new status |
| | | status = newStatus; |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + baseDn + |
| | | " new status is: " + status); |
| | | |
| | | // Perform whatever actions are needed to apply properties for being |
| | | // compliant with new status |
| | | updateDomainForNewStatus(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Perform whatever actions are needed to apply properties for being |
| | | * compliant with new status. Must be called in synchronized section for |
| | | * status. The new status is already set in status variable. |
| | | */ |
| | | private void updateDomainForNewStatus() |
| | | { |
| | | switch (status) |
| | | { |
| | | case NOT_CONNECTED_STATUS: |
| | | break; |
| | | case NORMAL_STATUS: |
| | | break; |
| | | case DEGRADED_STATUS: |
| | | break; |
| | | case FULL_UPDATE_STATUS: |
| | | // Signal RS we just entered the full update status |
| | | broker.signalStatusChange(status); |
| | | break; |
| | | case BAD_GEN_ID_STATUS: |
| | | break; |
| | | default: |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("updateDomainForNewStatus: unexpected status: " + |
| | | status); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Do the necessary processing when an UpdateMsg was received. |
| | | * |
| | | * @param update The received UpdateMsg. |
| | | */ |
| | | public void receiveUpdate(UpdateMsg update) |
| | | { |
| | | remotePendingChanges.putRemoteUpdate(update); |
| | | numRcvdUpdates.incrementAndGet(); |
| | | } |
| | | |
| | | /** |
| | | * Do the necessary processing when an AckMsg is received. |
| | | * |
| | | * @param ack The AckMsg that was received. |
| | | */ |
| | | public void receiveAck(AckMsg ack) |
| | | { |
| | | UpdateMsg update; |
| | | ChangeNumber changeNumber = ack.getChangeNumber(); |
| | | |
| | | synchronized (waitingAckMsgs) |
| | | { |
| | | update = waitingAckMsgs.remove(changeNumber); |
| | | } |
| | | if (update != null) |
| | | { |
| | | synchronized (update) |
| | | { |
| | | update.notify(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check if an operation must be synchronized. |
| | | * Also update the list of pending changes and the server RUV |
| | | * @param op the operation |
| | |
| | | { |
| | | numReplayedPostOpCalled++; |
| | | } |
| | | UpdateMsg msg = null; |
| | | LDAPUpdateMsg msg = null; |
| | | |
| | | // Note that a failed non-replication operation might not have a change |
| | | // number. |
| | | ChangeNumber curChangeNumber = OperationContext.getChangeNumber(op); |
| | | |
| | | boolean isAssured = isAssured(op); |
| | | |
| | | if ((result == ResultCode.SUCCESS) && (!op.isSynchronizationOperation())) |
| | | { |
| | | // Generate a replication message for a successful non-replication |
| | | // operation. |
| | | msg = UpdateMsg.generateMsg(op); |
| | | msg = LDAPUpdateMsg.generateMsg(op); |
| | | |
| | | if (msg == null) |
| | | { |
| | |
| | | return; |
| | | } |
| | | |
| | | if (msg != null && isAssured) |
| | | { |
| | | synchronized (waitingAckMsgs) |
| | | { |
| | | // Add the assured message to the list of update that are |
| | | // waiting acknowledgements |
| | | waitingAckMsgs.put(curChangeNumber, msg); |
| | | } |
| | | } |
| | | |
| | | if (generationIdSavedStatus != true) |
| | | { |
| | | this.saveGenerationId(generationId); |
| | |
| | | |
| | | if (!op.isSynchronizationOperation()) |
| | | { |
| | | int pushedChanges = pendingChanges.pushCommittedChanges(); |
| | | numSentUpdates.addAndGet(pushedChanges); |
| | | pendingChanges.pushCommittedChanges(); |
| | | } |
| | | |
| | | // Wait for acknowledgement of an assured message. |
| | | if (msg != null && isAssured) |
| | | { |
| | | synchronized (msg) |
| | | { |
| | | while (waitingAckMsgs.containsKey(msg.getChangeNumber())) |
| | | { |
| | | // TODO : should have a configurable timeout to get |
| | | // out of this loop |
| | | try |
| | | { |
| | | msg.wait(1000); |
| | | } catch (InterruptedException e) |
| | | { } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * get the number of updates received by the replication plugin. |
| | | * |
| | | * @return the number of updates received |
| | | */ |
| | | public int getNumRcvdUpdates() |
| | | { |
| | | if (numRcvdUpdates != null) |
| | | return numRcvdUpdates.get(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the number of updates sent by the replication plugin. |
| | | * |
| | | * @return the number of updates sent |
| | | */ |
| | | public int getNumSentUpdates() |
| | | { |
| | | if (numSentUpdates != null) |
| | | return numSentUpdates.get(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * Increment the number of processed updates. |
| | | */ |
| | | public void incProcessedUpdates() |
| | | { |
| | | numProcessedUpdates.incrementAndGet(); |
| | | } |
| | | |
| | | /** |
| | | * get the number of updates replayed by the replication. |
| | | * |
| | | * @return The number of updates replayed by the replication |
| | | */ |
| | | public int getNumProcessedUpdates() |
| | | { |
| | | if (numProcessedUpdates != null) |
| | | return numProcessedUpdates.get(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * get the number of updates replayed successfully by the replication. |
| | | * |
| | | * @return The number of updates replayed successfully |
| | |
| | | } |
| | | |
| | | /** |
| | | * get the ServerState. |
| | | * |
| | | * @return the ServerState |
| | | */ |
| | | public ServerState getServerState() |
| | | { |
| | | return state; |
| | | } |
| | | |
| | | /** |
| | | * Get the debugCount. |
| | | * |
| | | * @return Returns the debugCount. |
| | |
| | | } |
| | | |
| | | /** |
| | | * Send an Ack message. |
| | | * |
| | | * @param changeNumber The ChangeNumber for which the ack must be sent. |
| | | */ |
| | | public void ack(ChangeNumber changeNumber) |
| | | { |
| | | broker.publish(new AckMsg(changeNumber)); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void run() |
| | | { |
| | | done = false; |
| | | |
| | | // Create the listener thread |
| | | listenerThread = new ListenerThread(this, updateToReplayQueue); |
| | | listenerThread.start(); |
| | | |
| | | while (shutdown == false) |
| | | { |
| | | try |
| | | { |
| | | synchronized (this) |
| | | { |
| | | this.wait(1000); |
| | | if (!disabled && !stateSavingDisabled ) |
| | | { |
| | | // save the RUV |
| | | state.save(); |
| | | } |
| | | } |
| | | } catch (InterruptedException e) |
| | | { } |
| | | } |
| | | state.save(); |
| | | |
| | | done = true; |
| | | } |
| | | |
| | | /** |
| | | * Shutdown this ReplicationDomain. |
| | | */ |
| | | public void shutdown() |
| | |
| | | // stop the flush thread |
| | | shutdown = true; |
| | | |
| | | // Stop the listener thread |
| | | if (listenerThread != null) |
| | | // stop the thread in charge of flushing the ServerState. |
| | | if (flushThread != null) |
| | | { |
| | | listenerThread.shutdown(); |
| | | synchronized (flushThread) |
| | | { |
| | | flushThread.notify(); |
| | | } |
| | | } |
| | | |
| | | synchronized (this) |
| | | { |
| | | this.notify(); |
| | | } |
| | | |
| | | DirectoryServer.deregisterMonitorProvider(monitor.getMonitorInstanceName()); |
| | | |
| | | DirectoryServer.deregisterAlertGenerator(this); |
| | | |
| | | // stop the ReplicationBroker |
| | | broker.stop(); |
| | | |
| | | // Wait for the listener thread to stop |
| | | if (listenerThread != null) |
| | | listenerThread.waitForShutdown(); |
| | | // stop the ReplicationDomain |
| | | stopDomain(); |
| | | |
| | | // wait for completion of the persistentServerState thread. |
| | | try |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the name of the replicationServer to which this domain is currently |
| | | * connected. |
| | | * |
| | | * @return the name of the replicationServer to which this domain |
| | | * is currently connected. |
| | | */ |
| | | public String getReplicationServer() |
| | | { |
| | | if (broker != null) |
| | | return broker.getReplicationServer(); |
| | | else |
| | | return "Not connected"; |
| | | } |
| | | |
| | | /** |
| | | * Create and replay a synchronized Operation from an UpdateMsg. |
| | | * |
| | | * @param msg The UpdateMsg to be replayed. |
| | | */ |
| | | public void replay(UpdateMsg msg) |
| | | public void replay(LDAPUpdateMsg msg) |
| | | { |
| | | Operation op = null; |
| | | boolean done = false; |
| | |
| | | // whose dependency has been replayed until no more left. |
| | | do |
| | | { |
| | | String replayErrorMsg = null; |
| | | try |
| | | { |
| | | op = msg.createOperation(conn); |
| | |
| | | |
| | | while ((!dependency) && (!done) && (retryCount-- > 0)) |
| | | { |
| | | // Try replay the operation |
| | | op.setInternalOperation(true); |
| | | op.setSynchronizationOperation(true); |
| | | changeNumber = OperationContext.getChangeNumber(op); |
| | | ((AbstractOperation) op).run(); |
| | | |
| | | // Try replay the operation |
| | | ResultCode result = op.getResultCode(); |
| | | |
| | | if (result != ResultCode.SUCCESS) |
| | |
| | | op.getErrorMessage().toString()); |
| | | logError(message); |
| | | numUnresolvedNamingConflicts.incrementAndGet(); |
| | | |
| | | replayErrorMsg = message.toString(); |
| | | updateError(changeNumber); |
| | | } |
| | | } catch (ASN1Exception e) |
| | |
| | | Message message = ERR_EXCEPTION_DECODING_OPERATION.get( |
| | | String.valueOf(msg) + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | replayErrorMsg = message.toString(); |
| | | } catch (LDAPException e) |
| | | { |
| | | Message message = ERR_EXCEPTION_DECODING_OPERATION.get( |
| | | String.valueOf(msg) + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | replayErrorMsg = message.toString(); |
| | | } catch (DataFormatException e) |
| | | { |
| | | Message message = ERR_EXCEPTION_DECODING_OPERATION.get( |
| | | String.valueOf(msg) + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | replayErrorMsg = message.toString(); |
| | | } catch (Exception e) |
| | | { |
| | | if (changeNumber != null) |
| | |
| | | Message message = ERR_EXCEPTION_REPLAYING_OPERATION.get( |
| | | stackTraceToSingleLineString(e), op.toString()); |
| | | logError(message); |
| | | replayErrorMsg = message.toString(); |
| | | updateError(changeNumber); |
| | | } else |
| | | { |
| | | Message message = ERR_EXCEPTION_DECODING_OPERATION.get( |
| | | String.valueOf(msg) + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | replayErrorMsg = message.toString(); |
| | | } |
| | | } finally |
| | | { |
| | | if (!dependency) |
| | | { |
| | | broker.updateWindowAfterReplay(); |
| | | if (msg.isAssured()) |
| | | ack(msg.getChangeNumber()); |
| | | incProcessedUpdates(); |
| | | processUpdateDone(msg, replayErrorMsg); |
| | | } |
| | | } |
| | | |
| | |
| | | * @return true if the process is completed, false if it must continue.. |
| | | */ |
| | | private boolean solveNamingConflict(DeleteOperation op, |
| | | UpdateMsg msg) |
| | | LDAPUpdateMsg msg) |
| | | { |
| | | ResultCode result = op.getResultCode(); |
| | | DeleteContext ctx = (DeleteContext) op.getAttachment(SYNCHROCONTEXT); |
| | |
| | | * @throws Exception When the operation is not valid. |
| | | */ |
| | | private boolean solveNamingConflict(ModifyDNOperation op, |
| | | UpdateMsg msg) throws Exception |
| | | LDAPUpdateMsg msg) throws Exception |
| | | { |
| | | ResultCode result = op.getResultCode(); |
| | | ModifyDnContext ctx = (ModifyDnContext) op.getAttachment(SYNCHROCONTEXT); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Check if an operation must be processed as an assured operation. |
| | | * |
| | | * @param op the operation to be checked. |
| | | * @return true if the operations must be processed as an assured operation. |
| | | */ |
| | | private boolean isAssured(PostOperationOperation op) |
| | | { |
| | | // TODO : should have a filtering mechanism for checking |
| | | // operation that are assured and operations that are not. |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Get the maximum receive window size. |
| | | * |
| | | * @return The maximum receive window size. |
| | | */ |
| | | public int getMaxRcvWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getMaxRcvWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the current receive window size. |
| | | * |
| | | * @return The current receive window size. |
| | | */ |
| | | public int getCurrentRcvWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getCurrentRcvWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the maximum send window size. |
| | | * |
| | | * @return The maximum send window size. |
| | | */ |
| | | public int getMaxSendWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getMaxSendWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the current send window size. |
| | | * |
| | | * @return The current send window size. |
| | | */ |
| | | public int getCurrentSendWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getCurrentSendWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the number of times the replication connection was lost. |
| | | * @return The number of times the replication connection was lost. |
| | | */ |
| | | public int getNumLostConnections() |
| | | { |
| | | if (broker != null) |
| | | return broker.getNumLostConnections(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the number of modify conflicts successfully resolved. |
| | | * @return The number of modify conflicts successfully resolved. |
| | | */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the number of namign conflicts successfully resolved. |
| | | * Get the number of naming conflicts successfully resolved. |
| | | * @return The number of naming conflicts successfully resolved. |
| | | */ |
| | | public int getNumResolvedNamingConflicts() |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the server ID. |
| | | * @return The server ID. |
| | | */ |
| | | public short getServerId() |
| | | { |
| | | return serverId; |
| | | } |
| | | |
| | | /** |
| | | * Check if the domain solve conflicts. |
| | | * |
| | | * @return a boolean indicating if the domain should sove conflicts. |
| | | * @return a boolean indicating if the domain should solve conflicts. |
| | | */ |
| | | public boolean solveConflict() |
| | | { |
| | |
| | | state.save(); |
| | | state.clearInMemory(); |
| | | disabled = true; |
| | | |
| | | // Stop the listener thread |
| | | if (listenerThread != null) |
| | | listenerThread.shutdown(); |
| | | |
| | | broker.stop(); // This will cut the session and wake up the listener |
| | | |
| | | // Wait for the listener thread to stop |
| | | if (listenerThread != null) |
| | | listenerThread.waitForShutdown(); |
| | | disableService(); // This will cut the session and wake up the listener |
| | | } |
| | | |
| | | /** |
| | |
| | | return; |
| | | } |
| | | |
| | | // After an on-line import, the value of the generationId is new |
| | | // and it is necessary for the broker to send this new value as part |
| | | // of the serverStart message. |
| | | broker.setGenerationId(generationId); |
| | | |
| | | broker.start(replicationServers); |
| | | |
| | | // Create the listener thread |
| | | listenerThread = new ListenerThread(this, updateToReplayQueue); |
| | | listenerThread.start(); |
| | | enableService(); |
| | | |
| | | disabled = false; |
| | | } |
| | |
| | | */ |
| | | public long computeGenerationId() throws DirectoryException |
| | | { |
| | | long genId = exportBackend(true); |
| | | long genId = exportBackend(null, true); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Computed generationId: generationId=" + genId); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the generationId set for this domain. |
| | | * |
| | | * @return The generationId. |
| | | * {@inheritDoc} |
| | | */ |
| | | public long getGenerationId() |
| | | public long getGenerationID() |
| | | { |
| | | return generationId; |
| | | } |
| | |
| | | /** |
| | | * Stores the value of the generationId. |
| | | * @param generationId The value of the generationId. |
| | | * @return a ResultCode indicating if the method was successfull. |
| | | * @return a ResultCode indicating if the method was successful. |
| | | */ |
| | | public ResultCode saveGenerationId(long generationId) |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Reset the generationId of this domain in the whole topology. |
| | | * A message is sent to the Replication Servers for them to reset |
| | | * their change dbs. |
| | | * |
| | | * @param generationIdNewValue The new value of the generation Id. |
| | | * @throws DirectoryException when an error occurs |
| | | */ |
| | | public void resetGenerationId(Long generationIdNewValue) |
| | | throws DirectoryException |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | | this.getName() + "resetGenerationId" + generationIdNewValue); |
| | | |
| | | if (!isConnected()) |
| | | { |
| | | ResultCode resultCode = ResultCode.OTHER; |
| | | Message message = ERR_RESET_GENERATION_CONN_ERR_ID.get( |
| | | baseDn.toNormalizedString()); |
| | | throw new DirectoryException( |
| | | resultCode, message); |
| | | } |
| | | |
| | | ResetGenerationIdMsg genIdMessage = null; |
| | | |
| | | if (generationIdNewValue == null) |
| | | { |
| | | genIdMessage = new ResetGenerationIdMsg(this.generationId); |
| | | } |
| | | else |
| | | { |
| | | genIdMessage = new ResetGenerationIdMsg(generationIdNewValue); |
| | | } |
| | | broker.publish(genIdMessage); |
| | | } |
| | | |
| | | /** |
| | | * Do whatever is needed when a backup is started. |
| | | * We need to make sure that the serverState is correclty save. |
| | | * We need to make sure that the serverState is correctly save. |
| | | */ |
| | | public void backupStart() |
| | | { |
| | |
| | | * Total Update >> |
| | | */ |
| | | |
| | | /** |
| | | * Receives bytes related to an entry in the context of an import to |
| | | * initialize the domain (called by ReplLDIFInputStream). |
| | | * |
| | | * @return The bytes. Null when the Done or Err message has been received |
| | | */ |
| | | public byte[] receiveEntryBytes() |
| | | { |
| | | ReplicationMsg msg; |
| | | while (true) |
| | | { |
| | | try |
| | | { |
| | | msg = broker.receive(); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugVerbose( |
| | | " sid:" + this.serverId + |
| | | " base DN:" + this.baseDn + |
| | | " Import EntryBytes received " + msg); |
| | | if (msg == null) |
| | | { |
| | | // The server is in the shutdown process |
| | | return null; |
| | | } |
| | | |
| | | if (msg instanceof EntryMsg) |
| | | { |
| | | EntryMsg entryMsg = (EntryMsg)msg; |
| | | byte[] entryBytes = entryMsg.getEntryBytes(); |
| | | ieContext.updateCounters(); |
| | | return entryBytes; |
| | | } |
| | | else if (msg instanceof DoneMsg) |
| | | { |
| | | // This is the normal termination of the import |
| | | // No error is stored and the import is ended |
| | | // by returning null |
| | | return null; |
| | | } |
| | | else if (msg instanceof ErrorMsg) |
| | | { |
| | | // This is an error termination during the import |
| | | // The error is stored and the import is ended |
| | | // by returning null |
| | | ErrorMsg errorMsg = (ErrorMsg)msg; |
| | | ieContext.exception = new DirectoryException( |
| | | ResultCode.OTHER, |
| | | errorMsg.getDetails()); |
| | | return null; |
| | | } |
| | | else |
| | | { |
| | | // Other messages received during an import are trashed |
| | | } |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | // TODO: i18n |
| | | ieContext.exception = new DirectoryException(ResultCode.OTHER, |
| | | Message.raw("received an unexpected message type" + |
| | | e.getLocalizedMessage())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Processes an error message received while an import/export is |
| | | * on going. |
| | | * @param errorMsg The error message received. |
| | | */ |
| | | protected void abandonImportExport(ErrorMsg errorMsg) |
| | | { |
| | | // FIXME TBD Treat the case where the error happens while entries |
| | | // are being exported |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugVerbose( |
| | | " abandonImportExport:" + this.serverId + |
| | | " base DN:" + this.baseDn + |
| | | " Error Msg received " + errorMsg); |
| | | |
| | | if (ieContext != null) |
| | | { |
| | | ieContext.exception = new DirectoryException(ResultCode.OTHER, |
| | | errorMsg.getDetails()); |
| | | |
| | | if (ieContext.initializeTask instanceof InitializeTask) |
| | | { |
| | | // Update the task that initiated the import |
| | | ((InitializeTask)ieContext.initializeTask). |
| | | updateTaskCompletionState(ieContext.exception); |
| | | |
| | | releaseIEContext(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Clears all the entries from the JE backend determined by the |
| | |
| | | } |
| | | |
| | | /** |
| | | * This method trigger an export of the replicated data. |
| | | * |
| | | * @param output The OutputStream where the export should |
| | | * be produced. |
| | | * @throws DirectoryException When needed. |
| | | */ |
| | | protected void exportBackend(OutputStream output) throws DirectoryException |
| | | { |
| | | exportBackend(output, false); |
| | | } |
| | | |
| | | /** |
| | | * Export the entries from the backend and/or compute the generation ID. |
| | | * The ieContext must have been set before calling. |
| | | * @param checksumOutput true is the exportBackend is called to compute |
| | | * the generationID |
| | | * |
| | | * @return The computed generationID. |
| | | * @param output The OutputStream where the export should |
| | | * be produced. |
| | | * @param checksumOutput A boolean indicating if this export is |
| | | * invoked to perform a checksum only |
| | | * |
| | | * @return The computed GenerationID. |
| | | * |
| | | * @throws DirectoryException when an error occurred |
| | | */ |
| | | protected long exportBackend(boolean checksumOutput) |
| | | protected long exportBackend(OutputStream output, boolean checksumOutput) |
| | | throws DirectoryException |
| | | { |
| | | long genID = 0; |
| | |
| | | } |
| | | |
| | | OutputStream os; |
| | | ReplLDIFOutputStream ros; |
| | | ReplLDIFOutputStream ros = null; |
| | | |
| | | if (checksumOutput) |
| | | { |
| | |
| | | } |
| | | else |
| | | { |
| | | ros = new ReplLDIFOutputStream(this, (short)-1); |
| | | os = ros; |
| | | os = output; |
| | | } |
| | | LDIFExportConfig exportConfig = new LDIFExportConfig(os); |
| | | |
| | |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if ((checksumOutput) && |
| | | if ((ros != null) && |
| | | (ros.getNumExportedEntries() >= entryCount)) |
| | | { |
| | | // This is the normal end when computing the generationId |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the internal broker to perform some operations on it. |
| | | * |
| | | * @return The broker for this domain. |
| | | */ |
| | | ReplicationBroker getBroker() |
| | | { |
| | | return broker; |
| | | } |
| | | |
| | | /** |
| | | * Exports an entry in LDIF format. |
| | | * |
| | | * @param lDIFEntry The entry to be exported.. |
| | | * |
| | | * @throws IOException when an error occurred. |
| | | */ |
| | | public void exportLDIFEntry(String lDIFEntry) throws IOException |
| | | { |
| | | // If an error was raised - like receiving an ErrorMsg |
| | | // we just let down the export. |
| | | if (ieContext.exception != null) |
| | | { |
| | | IOException ioe = new IOException(ieContext.exception.getMessage()); |
| | | ieContext = null; |
| | | throw ioe; |
| | | } |
| | | |
| | | EntryMsg entryMessage = new EntryMsg( |
| | | serverId, ieContext.exportTarget, lDIFEntry.getBytes()); |
| | | broker.publish(entryMessage); |
| | | |
| | | try |
| | | { |
| | | ieContext.updateCounters(); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | throw new IOException(de.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Initializes this domain from another source server. |
| | | * |
| | | * @param source The source from which to initialize |
| | | * @param initTask The task that launched the initialization |
| | | * and should be updated of its progress. |
| | | * @throws DirectoryException when an error occurs |
| | | */ |
| | | public void initializeFromRemote(short source, Task initTask) |
| | | throws DirectoryException |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Entering initializeFromRemote"); |
| | | |
| | | acquireIEContext(); |
| | | ieContext.initializeTask = initTask; |
| | | |
| | | InitializeRequestMsg initializeMsg = new InitializeRequestMsg( |
| | | baseDn, serverId, source); |
| | | |
| | | // Publish Init request msg |
| | | broker.publish(initializeMsg); |
| | | |
| | | // .. we expect to receive entries or err after that |
| | | } |
| | | |
| | | /** |
| | | * Verifies that the given string represents a valid source |
| | | * from which this server can be initialized. |
| | | * @param sourceString The string representing the source |
| | | * @return The source as a short value |
| | | * @throws DirectoryException if the string is not valid |
| | | */ |
| | | public short decodeSource(String sourceString) |
| | | throws DirectoryException |
| | | { |
| | | short source = 0; |
| | | Throwable cause = null; |
| | | try |
| | | { |
| | | source = Integer.decode(sourceString).shortValue(); |
| | | if ((source >= -1) && (source != serverId)) |
| | | { |
| | | // TODO Verifies serverID is in the domain |
| | | // We shold check here that this is a server implied |
| | | // in the current domain. |
| | | return source; |
| | | } |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | cause = e; |
| | | } |
| | | |
| | | ResultCode resultCode = ResultCode.OTHER; |
| | | Message message = ERR_INVALID_IMPORT_SOURCE.get(); |
| | | if (cause != null) |
| | | { |
| | | throw new DirectoryException( |
| | | resultCode, message, cause); |
| | | } |
| | | else |
| | | { |
| | | throw new DirectoryException( |
| | | resultCode, message); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Verifies that the given string represents a valid source |
| | | * from which this server can be initialized. |
| | | * @param targetString The string representing the source |
| | | * @return The source as a short value |
| | | * @throws DirectoryException if the string is not valid |
| | | */ |
| | | public short decodeTarget(String targetString) |
| | | throws DirectoryException |
| | | { |
| | | short target = 0; |
| | | Throwable cause; |
| | | if (targetString.equalsIgnoreCase("all")) |
| | | { |
| | | return RoutableMsg.ALL_SERVERS; |
| | | } |
| | | |
| | | // So should be a serverID |
| | | try |
| | | { |
| | | target = Integer.decode(targetString).shortValue(); |
| | | if (target >= 0) |
| | | { |
| | | // FIXME Could we check now that it is a know server in the domain ? |
| | | } |
| | | return target; |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | cause = e; |
| | | } |
| | | ResultCode resultCode = ResultCode.OTHER; |
| | | Message message = ERR_INVALID_EXPORT_TARGET.get(); |
| | | |
| | | if (cause != null) |
| | | throw new DirectoryException( |
| | | resultCode, message, cause); |
| | | else |
| | | throw new DirectoryException( |
| | | resultCode, message); |
| | | |
| | | } |
| | | |
| | | private synchronized void acquireIEContext() |
| | | throws DirectoryException |
| | | { |
| | | if (ieContext != null) |
| | | { |
| | | // Rejects 2 simultaneous exports |
| | | Message message = ERR_SIMULTANEOUS_IMPORT_EXPORT_REJECTED.get(); |
| | | throw new DirectoryException(ResultCode.OTHER, |
| | | message); |
| | | } |
| | | |
| | | ieContext = new IEContext(); |
| | | } |
| | | |
| | | private synchronized void releaseIEContext() |
| | | { |
| | | ieContext = null; |
| | | } |
| | | |
| | | /** |
| | | * Process the initialization of some other server or servers in the topology |
| | | * specified by the target argument. |
| | | * @param target The target that should be initialized |
| | | * @param initTask The task that triggers this initialization and that should |
| | | * be updated with its progress. |
| | | * |
| | | * @exception DirectoryException When an error occurs. |
| | | */ |
| | | public void initializeRemote(short target, Task initTask) |
| | | throws DirectoryException |
| | | { |
| | | initializeRemote(target, serverId, initTask); |
| | | } |
| | | |
| | | /** |
| | | * Process the initialization of some other server or servers in the topology |
| | | * specified by the target argument when this initialization specifying the |
| | | * server that requests the initialization. |
| | | * |
| | | * @param target The target that should be initialized. |
| | | * @param requestorID The server that initiated the export. |
| | | * @param initTask The task that triggers this initialization and that should |
| | | * be updated with its progress. |
| | | * |
| | | * @exception DirectoryException When an error occurs. |
| | | */ |
| | | public void initializeRemote(short target, short requestorID, Task initTask) |
| | | throws DirectoryException |
| | | { |
| | | Message msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START.get( |
| | | Short.toString(serverId), |
| | | baseDn.toString(), |
| | | Short.toString(requestorID)); |
| | | logError(msg); |
| | | |
| | | boolean contextAcquired=false; |
| | | |
| | | try |
| | | { |
| | | Backend backend = retrievesBackend(this.baseDn); |
| | | |
| | | if (!backend.supportsLDIFExport()) |
| | | { |
| | | Message message = ERR_INIT_EXPORT_NOT_SUPPORTED.get( |
| | | backend.getBackendID().toString()); |
| | | logError(message); |
| | | throw new DirectoryException(ResultCode.OTHER, message); |
| | | } |
| | | |
| | | acquireIEContext(); |
| | | contextAcquired = true; |
| | | |
| | | // The number of entries to be exported is the number of entries under |
| | | // the base DN entry and the base entry itself. |
| | | long entryCount = backend.numSubordinates(baseDn, true) + 1; |
| | | ieContext.exportTarget = target; |
| | | if (initTask != null) |
| | | { |
| | | ieContext.initializeTask = initTask; |
| | | } |
| | | ieContext.setCounters(entryCount, entryCount); |
| | | |
| | | // Send start message to the peer |
| | | InitializeTargetMsg initializeMessage = new InitializeTargetMsg( |
| | | baseDn, serverId, ieContext.exportTarget, requestorID, entryCount); |
| | | |
| | | broker.publish(initializeMessage); |
| | | |
| | | exportBackend(false); |
| | | |
| | | // Notify the peer of the success |
| | | DoneMsg doneMsg = new DoneMsg(serverId, |
| | | initializeMessage.getDestination()); |
| | | broker.publish(doneMsg); |
| | | |
| | | releaseIEContext(); |
| | | } |
| | | catch(DirectoryException de) |
| | | { |
| | | // Notify the peer of the failure |
| | | ErrorMsg errorMsg = |
| | | new ErrorMsg(target, |
| | | de.getMessageObject()); |
| | | broker.publish(errorMsg); |
| | | |
| | | if (contextAcquired) |
| | | releaseIEContext(); |
| | | |
| | | throw(de); |
| | | } |
| | | |
| | | msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_END.get( |
| | | Short.toString(serverId), |
| | | baseDn.toString(), |
| | | Short.toString(requestorID)); |
| | | logError(msg); |
| | | } |
| | | |
| | | /** |
| | | * Process backend before import. |
| | | * @param backend The backend. |
| | | * @throws Exception |
| | |
| | | } |
| | | |
| | | /** |
| | | * Initializes the domain's backend with received entries. |
| | | * @param initializeMessage The message that initiated the import. |
| | | * @exception DirectoryException Thrown when an error occurs. |
| | | * This method should trigger an import of the replicated data. |
| | | * |
| | | * @param input The InputStream from which |
| | | * @throws DirectoryException When needed. |
| | | */ |
| | | protected void initialize(InitializeTargetMsg initializeMessage) |
| | | throws DirectoryException |
| | | public void importBackend(InputStream input) throws DirectoryException |
| | | { |
| | | LDIFImportConfig importConfig = null; |
| | | DirectoryException de = null; |
| | | |
| | | Message msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_START.get( |
| | | Short.toString(serverId), |
| | | baseDn.toString(), |
| | | Long.toString(initializeMessage.getRequestorID())); |
| | | logError(msg); |
| | | |
| | | // Go into full update status |
| | | // Use synchronized as somebody could ask another status change at the same |
| | | // time |
| | | synchronized (status) |
| | | { |
| | | StatusMachineEvent event = StatusMachineEvent.TO_FULL_UPDATE_STATUS_EVENT; |
| | | ServerStatus newStatus = |
| | | StatusMachine.computeNewStatus(status, event); |
| | | |
| | | if (newStatus == ServerStatus.INVALID_STATUS) |
| | | { |
| | | msg = ERR_DS_CANNOT_CHANGE_STATUS.get(baseDn.toString(), |
| | | Short.toString(serverId), status.toString(), event.toString()); |
| | | logError(msg); |
| | | return; |
| | | } |
| | | |
| | | // Store new status |
| | | status = newStatus; |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + baseDn + |
| | | " new status is: " + status); |
| | | |
| | | // Perform whatever actions are needed to apply properties for being |
| | | // compliant with new status |
| | | updateDomainForNewStatus(); |
| | | } |
| | | |
| | | Backend backend = retrievesBackend(baseDn); |
| | | |
| | | try |
| | |
| | | } |
| | | else |
| | | { |
| | | if (initializeMessage.getRequestorID() == serverId) |
| | | { |
| | | // The import responds to a request we did so the IEContext |
| | | // is already acquired |
| | | } |
| | | else |
| | | { |
| | | acquireIEContext(); |
| | | } |
| | | |
| | | ieContext.importSource = initializeMessage.getsenderID(); |
| | | ieContext.entryLeftCount = initializeMessage.getEntryCount(); |
| | | ieContext.setCounters(initializeMessage.getEntryCount(), |
| | | initializeMessage.getEntryCount()); |
| | | |
| | | preBackendImport(backend); |
| | | |
| | | ieContext.ldifImportInputStream = new ReplLDIFInputStream(this); |
| | | importConfig = |
| | | new LDIFImportConfig(ieContext.ldifImportInputStream); |
| | | new LDIFImportConfig(input); |
| | | List<DN> includeBranches = new ArrayList<DN>(); |
| | | includeBranches.add(this.baseDn); |
| | | importConfig.setIncludeBranches(includeBranches); |
| | |
| | | |
| | | // TODO How to deal with rejected entries during the import |
| | | importConfig.writeRejectedEntries( |
| | | getFileForPath("logs" + File.separator + |
| | | "replInitRejectedEntries").getAbsolutePath(), |
| | | ExistingFileBehavior.OVERWRITE); |
| | | getFileForPath("logs" + File.separator + |
| | | "replInitRejectedEntries").getAbsolutePath(), |
| | | ExistingFileBehavior.OVERWRITE); |
| | | |
| | | // Process import |
| | | preBackendImport(backend); |
| | | backend.importLDIF(importConfig); |
| | | |
| | | stateSavingDisabled = false; |
| | |
| | | } |
| | | finally |
| | | { |
| | | if ((ieContext != null) && (ieContext.exception != null)) |
| | | de = ieContext.exception; |
| | | |
| | | // Cleanup |
| | | if (importConfig != null) |
| | | { |
| | |
| | | TRACER.debugInfo( |
| | | "After import, the replication plugin restarts connections" + |
| | | " to all RSs to provide new generation ID=" + generationId); |
| | | broker.setGenerationId(generationId); |
| | | } |
| | | catch (DirectoryException fe) |
| | | { |
| | |
| | | if (de == null) |
| | | de = fe; |
| | | } |
| | | |
| | | // Re-exchange generationID and state with RS |
| | | broker.reStart(); |
| | | |
| | | // Update the task that initiated the import |
| | | if ((ieContext != null ) && (ieContext.initializeTask != null)) |
| | | { |
| | | ((InitializeTask)ieContext.initializeTask). |
| | | updateTaskCompletionState(de); |
| | | } |
| | | releaseIEContext(); |
| | | } |
| | | // Sends up the root error. |
| | | if (de != null) |
| | | { |
| | | throw de; |
| | | } |
| | | |
| | | msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_END.get( |
| | | Short.toString(serverId), |
| | | baseDn.toString(), |
| | | Long.toString(initializeMessage.getRequestorID())); |
| | | logError(msg); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @throws DirectoryException When an error occurred or no domain |
| | | * match the provided baseDn. |
| | | */ |
| | | public static ReplicationDomain retrievesReplicationDomain(DN baseDn) |
| | | public static LDAPReplicationDomain retrievesReplicationDomain(DN baseDn) |
| | | throws DirectoryException |
| | | { |
| | | ReplicationDomain replicationDomain = null; |
| | | LDAPReplicationDomain replicationDomain = null; |
| | | |
| | | // Retrieves the domain |
| | | DirectoryServer.getSynchronizationProviders(); |
| | |
| | | } |
| | | |
| | | // From the domainDN retrieves the replication domain |
| | | ReplicationDomain sdomain = |
| | | LDAPReplicationDomain sdomain = |
| | | MultimasterReplication.findDomain(baseDn, null); |
| | | if (sdomain == null) |
| | | { |
| | |
| | | return retrievesBackend(baseDn); |
| | | } |
| | | |
| | | /** |
| | | * Returns a boolean indicating if an import or export is currently |
| | | * processed. |
| | | * @return The status |
| | | */ |
| | | public boolean ieRunning() |
| | | { |
| | | return (ieContext != null); |
| | | } |
| | | /* |
| | | * <<Total Update |
| | | */ |
| | |
| | | { |
| | | // Check that there is not already a domain with the same DN |
| | | DN dn = configuration.getBaseDN(); |
| | | ReplicationDomain domain = MultimasterReplication.findDomain(dn, null); |
| | | LDAPReplicationDomain domain = MultimasterReplication.findDomain(dn, null); |
| | | if ((domain != null) && (domain.baseDn.equals(dn))) |
| | | { |
| | | Message message = ERR_SYNC_INVALID_DN.get(); |
| | |
| | | public ConfigChangeResult applyConfigurationChange( |
| | | ReplicationDomainCfg configuration) |
| | | { |
| | | // server id and base dn are readonly. |
| | | // isolationPolicy can be set immediately and will apply |
| | | // to the next updates. |
| | | // The other parameters needs to be renegociated with the ReplicationServer |
| | | // so that requires restarting the session with the ReplicationServer. |
| | | Boolean needToRestartSession = false; |
| | | Collection<String> newReplServers = configuration.getReplicationServer(); |
| | | |
| | | // A new session is necessary only when information regarding |
| | | // the connection is modified |
| | | if ((!(replicationServers.size() == newReplServers.size() |
| | | && replicationServers.containsAll(newReplServers))) || |
| | | window != configuration.getWindowSize() || |
| | | heartbeatInterval != configuration.getHeartbeatInterval()) |
| | | needToRestartSession = true; |
| | | |
| | | replicationServers = newReplServers; |
| | | window = configuration.getWindowSize(); |
| | | heartbeatInterval = configuration.getHeartbeatInterval(); |
| | | broker.changeConfig(replicationServers, maxReceiveQueue, maxReceiveDelay, |
| | | maxSendQueue, maxSendDelay, window, heartbeatInterval); |
| | | isolationpolicy = configuration.getIsolationPolicy(); |
| | | |
| | | // To be able to stop and restart the broker properly just |
| | | // disable and enable the domain. That way a new session |
| | | // with the new configuration is available. |
| | | if (needToRestartSession) |
| | | { |
| | | this.disable(); |
| | | this.enable(); |
| | | } |
| | | changeConfig( |
| | | configuration.getReplicationServer(), |
| | | configuration.getWindowSize(), |
| | | configuration.getHeartbeatInterval()); |
| | | |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Check if the domain is connected to a ReplicationServer. |
| | | * Starts the Replication Domain. |
| | | */ |
| | | public void start() |
| | | { |
| | | // Create the ServerStateFlush thread |
| | | flushThread = new ServerStateFlush(); |
| | | flushThread.start(); |
| | | |
| | | startListenService(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void sessionInitiated( |
| | | ServerStatus initStatus, |
| | | ServerState replicationServerState, |
| | | ProtocolSession session) |
| | | { |
| | | super.sessionInitiated(initStatus, replicationServerState, session); |
| | | try |
| | | { |
| | | /* |
| | | * We must not publish changes to a replicationServer that has |
| | | * not seen all our previous changes because this could cause |
| | | * some other ldap servers to miss those changes. |
| | | * Check that the ReplicationServer has seen all our previous |
| | | * changes. |
| | | */ |
| | | ChangeNumber replServerMaxChangeNumber = |
| | | replicationServerState.getMaxChangeNumber(serverId); |
| | | |
| | | if (replServerMaxChangeNumber == null) |
| | | { |
| | | replServerMaxChangeNumber = new ChangeNumber(0, 0, serverId); |
| | | } |
| | | ChangeNumber ourMaxChangeNumber = |
| | | state.getMaxChangeNumber(serverId); |
| | | |
| | | if ((ourMaxChangeNumber != null) && |
| | | (!ourMaxChangeNumber.olderOrEqual(replServerMaxChangeNumber))) |
| | | { |
| | | |
| | | // Replication server is missing some of our changes: let's |
| | | // send them to him. |
| | | |
| | | Message message = DEBUG_GOING_TO_SEARCH_FOR_CHANGES.get(); |
| | | logError(message); |
| | | |
| | | /* |
| | | * Get all the changes that have not been seen by this |
| | | * replication server and populate the replayOperations |
| | | * list. |
| | | */ |
| | | InternalSearchOperation op = searchForChangedEntries( |
| | | baseDn, replServerMaxChangeNumber, this); |
| | | if (op.getResultCode() != ResultCode.SUCCESS) |
| | | { |
| | | /* |
| | | * An error happened trying to search for the updates |
| | | * This server will start accepting again new updates but |
| | | * some inconsistencies will stay between servers. |
| | | * Log an error for the repair tool |
| | | * that will need to re-synchronize the servers. |
| | | */ |
| | | message = ERR_CANNOT_RECOVER_CHANGES.get( |
| | | baseDn.toNormalizedString()); |
| | | logError(message); |
| | | } else |
| | | { |
| | | for (FakeOperation replayOp : replayOperations) |
| | | { |
| | | ChangeNumber cn = replayOp.getChangeNumber(); |
| | | /* |
| | | * Because the entry returned by the search operation |
| | | * can contain old historical information, it is |
| | | * possible that some of the FakeOperation are |
| | | * actually older than the |
| | | * Only send the Operation if it was newer than |
| | | * the last ChangeNumber known by the Replication Server. |
| | | */ |
| | | if (cn.newer(replServerMaxChangeNumber)) |
| | | { |
| | | message = |
| | | DEBUG_SENDING_CHANGE.get( |
| | | replayOp.getChangeNumber().toString()); |
| | | logError(message); |
| | | session.publish(replayOp.generateMessage()); |
| | | } |
| | | } |
| | | message = DEBUG_CHANGES_SENT.get(); |
| | | logError(message); |
| | | } |
| | | replayOperations.clear(); |
| | | } |
| | | } catch (Exception e) |
| | | { |
| | | Message message = ERR_PUBLISHING_FAKE_OPS.get( |
| | | baseDn.toNormalizedString(), |
| | | e.getLocalizedMessage() + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Search for the changes that happened since fromChangeNumber |
| | | * based on the historical attribute. The only changes that will |
| | | * be send will be the one generated on the serverId provided in |
| | | * fromChangeNumber. |
| | | * @param baseDn the base DN |
| | | * @param fromChangeNumber The change number from which we want the changes |
| | | * @param resultListener that will process the entries returned. |
| | | * @return the internal search operation |
| | | * @throws Exception when raised. |
| | | */ |
| | | public static InternalSearchOperation searchForChangedEntries( |
| | | DN baseDn, |
| | | ChangeNumber fromChangeNumber, |
| | | InternalSearchListener resultListener) |
| | | throws Exception |
| | | { |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | Short serverId = fromChangeNumber.getServerId(); |
| | | |
| | | String maxValueForId = "ffffffffffffffff" + |
| | | String.format("%04x", serverId) + "ffffffff"; |
| | | |
| | | LDAPFilter filter = LDAPFilter.decode( |
| | | "(&(" + Historical.HISTORICALATTRIBUTENAME + ">=dummy:" |
| | | + fromChangeNumber + ")(" + Historical.HISTORICALATTRIBUTENAME + |
| | | "<=dummy:" + maxValueForId + "))"); |
| | | |
| | | LinkedHashSet<String> attrs = new LinkedHashSet<String>(1); |
| | | attrs.add(Historical.HISTORICALATTRIBUTENAME); |
| | | attrs.add(Historical.ENTRYUIDNAME); |
| | | attrs.add("*"); |
| | | return conn.processSearch( |
| | | new ASN1OctetString(baseDn.toString()), |
| | | SearchScope.WHOLE_SUBTREE, |
| | | DereferencePolicy.NEVER_DEREF_ALIASES, |
| | | 0, 0, false, filter, |
| | | attrs, |
| | | resultListener); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void handleInternalSearchEntry( |
| | | InternalSearchOperation searchOperation, |
| | | SearchResultEntry searchEntry) |
| | | { |
| | | /* |
| | | * This call back is called at session establishment phase |
| | | * for each entry that has been changed by this server and the changes |
| | | * have not been sent to any Replication Server. |
| | | * The role of this method is to build equivalent operation from |
| | | * the historical information and add them in the replayOperations |
| | | * table. |
| | | */ |
| | | Iterable<FakeOperation> updates = |
| | | Historical.generateFakeOperations(searchEntry); |
| | | for (FakeOperation op : updates) |
| | | { |
| | | replayOperations.add(op); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void handleInternalSearchReference( |
| | | InternalSearchOperation searchOperation, |
| | | SearchResultReference searchReference) |
| | | { |
| | | // TODO to be implemented |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This method should return the total number of objects in the |
| | | * replicated domain. |
| | | * This count will be used for reporting. |
| | | * |
| | | * @return true if the server is connected, false if not. |
| | | * @throws DirectoryException when needed. |
| | | * |
| | | * @return The number of objects in the replication domain. |
| | | */ |
| | | public boolean isConnected() |
| | | public long countEntries() throws DirectoryException |
| | | { |
| | | if (broker != null) |
| | | return broker.isConnected(); |
| | | else |
| | | Backend backend = retrievesBackend(baseDn); |
| | | |
| | | if (!backend.supportsLDIFExport()) |
| | | { |
| | | Message message = ERR_INIT_EXPORT_NOT_SUPPORTED.get( |
| | | backend.getBackendID().toString()); |
| | | logError(message); |
| | | throw new DirectoryException(ResultCode.OTHER, message); |
| | | } |
| | | |
| | | return backend.numSubordinates(baseDn, true) + 1; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean processUpdate(UpdateMsg updateMsg) |
| | | { |
| | | if (updateMsg instanceof LDAPUpdateMsg) |
| | | { |
| | | LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg; |
| | | |
| | | // put the UpdateMsg in the RemotePendingChanges list. |
| | | remotePendingChanges.putRemoteUpdate(msg); |
| | | |
| | | // Put update message into the replay queue |
| | | // (block until some place in the queue is available) |
| | | UpdateToReplay updateToReplay = new UpdateToReplay(msg, this); |
| | | updateToReplayQueue.offer(updateToReplay); |
| | | |
| | | return false; |
| | | } |
| | | |
| | | // unknown message type, this should not happen, just ignore it. |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Determine whether the connection to the replication server is encrypted. |
| | | * @return true if the connection is encrypted, false otherwise. |
| | | * Monitoring information for the LDAPReplicationDomain. |
| | | * |
| | | * @return Monitoring attributes specific to the LDAPReplicationDomain. |
| | | */ |
| | | public boolean isSessionEncrypted() |
| | | public Collection<Attribute> getAdditionalMonitoring() |
| | | { |
| | | if (broker != null) |
| | | return broker.isSessionEncrypted(); |
| | | else |
| | | return false; |
| | | } |
| | | ArrayList<Attribute> attributes = new ArrayList<Attribute>(); |
| | | |
| | | /** |
| | | * Gets the info for DSs in the topology (except us). |
| | | * @return The info for DSs in the topology (except us) |
| | | */ |
| | | public List<DSInfo> getDsList() |
| | | { |
| | | return dsList; |
| | | } |
| | | /* get number of changes in the pending list */ |
| | | ReplicationMonitor.addMonitorData( |
| | | attributes, "pending-updates", getPendingUpdatesCount()); |
| | | |
| | | /** |
| | | * Gets the info for RSs in the topology (except the one we are connected |
| | | * to). |
| | | * @return The info for RSs in the topology (except the one we are connected |
| | | * to) |
| | | */ |
| | | public List<RSInfo> getRsList() |
| | | { |
| | | return rsList; |
| | | } |
| | | /* get number of changes successfully */ |
| | | ReplicationMonitor.addMonitorData(attributes, "replayed-updates-ok", |
| | | getNumReplayedPostOpCalled()); |
| | | |
| | | /** |
| | | * Tells if assured replication is enabled for this domain. |
| | | * @return True if assured replication is enabled for this domain. |
| | | */ |
| | | public boolean isAssured() |
| | | { |
| | | return assured; |
| | | } |
| | | /* get number of modify conflicts */ |
| | | ReplicationMonitor.addMonitorData(attributes, "resolved-modify-conflicts", |
| | | getNumResolvedModifyConflicts()); |
| | | |
| | | /** |
| | | * Gives the mode for the assured replication of the domain. |
| | | * @return The mode for the assured replication of the domain. |
| | | */ |
| | | public AssuredMode getAssuredMode() |
| | | { |
| | | return assuredMode; |
| | | } |
| | | /* get number of naming conflicts */ |
| | | ReplicationMonitor.addMonitorData(attributes, "resolved-naming-conflicts", |
| | | getNumResolvedNamingConflicts()); |
| | | |
| | | /** |
| | | * Gives the assured level of the replication of the domain. |
| | | * @return The assured level of the replication of the domain. |
| | | */ |
| | | public byte getAssuredSdLevel() |
| | | { |
| | | return assuredSdLevel; |
| | | } |
| | | /* get number of unresolved naming conflicts */ |
| | | ReplicationMonitor.addMonitorData(attributes, "unresolved-naming-conflicts", |
| | | getNumUnresolvedNamingConflicts()); |
| | | |
| | | /** |
| | | * Gets the group id for this domain. |
| | | * @return The group id for this domain. |
| | | */ |
| | | public byte getGroupId() |
| | | { |
| | | return groupId; |
| | | } |
| | | |
| | | /** |
| | | * Gets the referrals URLs this domain publishes. |
| | | * @return The referrals URLs this domain publishes. |
| | | */ |
| | | public List<String> getRefUrls() |
| | | { |
| | | return refUrls; |
| | | } |
| | | |
| | | /** |
| | | * Gets the status for this domain. |
| | | * @return The status for this domain. |
| | | */ |
| | | public ServerStatus getStatus() |
| | | { |
| | | return status; |
| | | return attributes; |
| | | } |
| | | } |
| | |
| | | ExportTaskListener |
| | | { |
| | | private ReplicationServerListener replicationServerListener = null; |
| | | private static Map<DN, ReplicationDomain> domains = |
| | | new HashMap<DN, ReplicationDomain>() ; |
| | | private static Map<DN, LDAPReplicationDomain> domains = |
| | | new HashMap<DN, LDAPReplicationDomain>() ; |
| | | |
| | | /** |
| | | * The queue of received update messages, to be treated by the ReplayThread |
| | |
| | | * Can be null is the request has no associated operation. |
| | | * @return The domain for this DN. |
| | | */ |
| | | public static ReplicationDomain findDomain(DN dn, PluginOperation pluginOp) |
| | | public static LDAPReplicationDomain findDomain( |
| | | DN dn, PluginOperation pluginOp) |
| | | { |
| | | /* |
| | | * Don't run the special replication code on Operation that are |
| | |
| | | } |
| | | |
| | | |
| | | ReplicationDomain domain = null; |
| | | LDAPReplicationDomain domain = null; |
| | | DN temp = dn; |
| | | do |
| | | { |
| | |
| | | * @return The domain created. |
| | | * @throws ConfigException When the configuration is not valid. |
| | | */ |
| | | public static ReplicationDomain createNewDomain( |
| | | public static LDAPReplicationDomain createNewDomain( |
| | | ReplicationDomainCfg configuration) |
| | | throws ConfigException |
| | | { |
| | | ReplicationDomain domain; |
| | | domain = new ReplicationDomain(configuration, updateToReplayQueue); |
| | | LDAPReplicationDomain domain; |
| | | domain = new LDAPReplicationDomain(configuration, updateToReplayQueue); |
| | | |
| | | if (domains.size() == 0) |
| | | { |
| | |
| | | */ |
| | | public static void deleteDomain(DN dn) |
| | | { |
| | | ReplicationDomain domain = domains.remove(dn); |
| | | LDAPReplicationDomain domain = domains.remove(dn); |
| | | |
| | | if (domain != null) |
| | | domain.shutdown(); |
| | |
| | | public boolean isConfigurationAddAcceptable( |
| | | ReplicationDomainCfg configuration, List<Message> unacceptableReasons) |
| | | { |
| | | return ReplicationDomain.isConfigurationAcceptable( |
| | | return LDAPReplicationDomain.isConfigurationAcceptable( |
| | | configuration, unacceptableReasons); |
| | | } |
| | | |
| | |
| | | { |
| | | try |
| | | { |
| | | ReplicationDomain rd = createNewDomain(configuration); |
| | | LDAPReplicationDomain rd = createNewDomain(configuration); |
| | | if (isRegistered) |
| | | { |
| | | rd.start(); |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | PreOperationModifyOperation modifyOperation) |
| | | { |
| | | ReplicationDomain domain = |
| | | LDAPReplicationDomain domain = |
| | | findDomain(modifyOperation.getEntryDN(), modifyOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | PreOperationAddOperation addOperation) throws DirectoryException |
| | | { |
| | | ReplicationDomain domain = |
| | | LDAPReplicationDomain domain = |
| | | findDomain(addOperation.getEntryDN(), addOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | PreOperationDeleteOperation deleteOperation) throws DirectoryException |
| | | { |
| | | ReplicationDomain domain = |
| | | LDAPReplicationDomain domain = |
| | | findDomain(deleteOperation.getEntryDN(), deleteOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | PreOperationModifyDNOperation modifyDNOperation) throws DirectoryException |
| | | { |
| | | ReplicationDomain domain = |
| | | LDAPReplicationDomain domain = |
| | | findDomain(modifyDNOperation.getEntryDN(), modifyDNOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | doPreOperation(PreOperationModifyOperation modifyOperation) |
| | | { |
| | | DN operationDN = modifyOperation.getEntryDN(); |
| | | ReplicationDomain domain = findDomain(operationDN, modifyOperation); |
| | | LDAPReplicationDomain domain = findDomain(operationDN, modifyOperation); |
| | | |
| | | if ((domain == null) || (!domain.solveConflict())) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | throws DirectoryException |
| | | { |
| | | DN operationDN = modifyDNOperation.getEntryDN(); |
| | | ReplicationDomain domain = findDomain(operationDN, modifyDNOperation); |
| | | LDAPReplicationDomain domain = findDomain(operationDN, modifyDNOperation); |
| | | |
| | | if ((domain == null) || (!domain.solveConflict())) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | public SynchronizationProviderResult doPreOperation( |
| | | PreOperationAddOperation addOperation) |
| | | { |
| | | ReplicationDomain domain = |
| | | LDAPReplicationDomain domain = |
| | | findDomain(addOperation.getEntryDN(), addOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult.ContinueProcessing(); |
| | |
| | | isRegistered = false; |
| | | |
| | | // shutdown all the domains |
| | | for (ReplicationDomain domain : domains.values()) |
| | | for (LDAPReplicationDomain domain : domains.values()) |
| | | { |
| | | domain.shutdown(); |
| | | } |
| | |
| | | @Override |
| | | public void processSchemaChange(List<Modification> modifications) |
| | | { |
| | | ReplicationDomain domain = |
| | | LDAPReplicationDomain domain = |
| | | findDomain(DirectoryServer.getSchemaDN(), null); |
| | | if (domain != null) |
| | | domain.synchronizeModifications(modifications); |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.backupStart(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.backupEnd(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.disable(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.enable(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.disable(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.enable(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.backupStart(); |
| | | } |
| | |
| | | { |
| | | for (DN dn : backend.getBaseDNs()) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, null); |
| | | LDAPReplicationDomain domain = findDomain(dn, null); |
| | | if (domain != null) |
| | | domain.backupEnd(); |
| | | } |
| | |
| | | */ |
| | | private void genericPostOperation(PostOperationOperation operation, DN dn) |
| | | { |
| | | ReplicationDomain domain = findDomain(dn, operation); |
| | | LDAPReplicationDomain domain = findDomain(dn, operation); |
| | | if (domain == null) |
| | | return; |
| | | |
| | |
| | | isRegistered = true; |
| | | |
| | | // start all the domains |
| | | for (ReplicationDomain domain : domains.values()) |
| | | for (LDAPReplicationDomain domain : domains.values()) |
| | | { |
| | | domain.start(); |
| | | } |
| | |
| | | |
| | | 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.replication.protocol.LDAPUpdateMsg; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.operation.PluginOperation; |
| | |
| | | { |
| | | private ChangeNumber changeNumber; |
| | | private boolean committed; |
| | | private UpdateMsg msg; |
| | | private LDAPUpdateMsg msg; |
| | | private PluginOperation op; |
| | | private ServerState dependencyState = null; |
| | | private DN targetDN = null; |
| | |
| | | */ |
| | | public PendingChange(ChangeNumber changeNumber, |
| | | PluginOperation op, |
| | | UpdateMsg msg) |
| | | LDAPUpdateMsg msg) |
| | | { |
| | | this.changeNumber = changeNumber; |
| | | this.committed = false; |
| | |
| | | * @return the message if operation was a replication operation |
| | | * null if the operation was a local operation |
| | | */ |
| | | public UpdateMsg getMsg() |
| | | public LDAPUpdateMsg getMsg() |
| | | { |
| | | return msg; |
| | | } |
| | |
| | | * Set the message associated to the PendingChange. |
| | | * @param msg the message |
| | | */ |
| | | public void setMsg(UpdateMsg msg) |
| | | public void setMsg(LDAPUpdateMsg msg) |
| | | { |
| | | this.msg = msg; |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | |
| | | import java.util.NoSuchElementException; |
| | | import java.util.SortedMap; |
| | | import java.util.TreeMap; |
| | | |
| | | import java.util.concurrent.TimeoutException; |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.replication.service.ReplicationDomain; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | import org.opends.server.types.operation.PluginOperation; |
| | | |
| | | /** |
| | |
| | | private ChangeNumberGenerator changeNumberGenerator; |
| | | |
| | | /** |
| | | * The Replicationbroker that will be used to send UpdateMsg. |
| | | * The ReplicationDomain that will be used to send UpdateMsg. |
| | | */ |
| | | private ReplicationBroker broker; |
| | | |
| | | /** |
| | | * The ServerState that will be updated when UpdateMsg are committed. |
| | | */ |
| | | private ServerState state; |
| | | private ReplicationDomain domain; |
| | | |
| | | /** |
| | | * Creates a new PendingChanges using the provided ChangeNumberGenerator. |
| | | * |
| | | * @param changeNumberGenerator The ChangeNumberGenerator to use to create |
| | | * new unique ChangeNumbers. |
| | | * @param broker The Replicationbroker that will be used to send |
| | | * @param domain The ReplicationDomain that will be used to send |
| | | * UpdateMsg. |
| | | * @param state The ServerState that will be updated when UpdateMsg |
| | | * are committed. |
| | | */ |
| | | public PendingChanges( |
| | | ChangeNumberGenerator changeNumberGenerator, ReplicationBroker broker, |
| | | ServerState state) |
| | | ChangeNumberGenerator changeNumberGenerator, ReplicationDomain domain) |
| | | { |
| | | this.changeNumberGenerator = changeNumberGenerator; |
| | | this.broker = broker; |
| | | this.state = state; |
| | | this.domain = domain; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return The UpdateMsg that was just removed. |
| | | */ |
| | | public synchronized UpdateMsg remove(ChangeNumber changeNumber) |
| | | public synchronized LDAPUpdateMsg remove(ChangeNumber changeNumber) |
| | | { |
| | | return pendingChanges.remove(changeNumber).getMsg(); |
| | | } |
| | |
| | | * @param msg The message associated to the update. |
| | | */ |
| | | public synchronized void commit(ChangeNumber changeNumber, |
| | | UpdateMsg msg) |
| | | LDAPUpdateMsg msg) |
| | | { |
| | | PendingChange curChange = pendingChanges.get(changeNumber); |
| | | if (curChange == null) |
| | |
| | | (firstChange.getOp().isSynchronizationOperation() == false)) |
| | | { |
| | | numSentUpdates++; |
| | | broker.publish(firstChange.getMsg()); |
| | | } |
| | | state.update(firstChangeNumber); |
| | | LDAPUpdateMsg updateMsg = firstChange.getMsg(); |
| | | try |
| | | { |
| | | domain.publish(updateMsg); |
| | | } catch (TimeoutException ex) { |
| | | // This exception may only be raised if assured replication is |
| | | // enabled |
| | | Message errorMsg = ERR_DS_ACK_TIMEOUT.get( |
| | | domain.getServiceID(), Long.toString(domain.getAssuredTimeout()), |
| | | updateMsg.toString()); |
| | | logError(errorMsg); |
| | | } |
| | | } |
| | | pendingChanges.remove(firstChangeNumber); |
| | | |
| | | if (pendingChanges.isEmpty()) |
| | |
| | | * used to store the synchronized data and that is therefore persistent |
| | | * across server reboot. |
| | | */ |
| | | public class PersistentServerState extends ServerState |
| | | public class PersistentServerState |
| | | { |
| | | private DN baseDn; |
| | | private boolean savedStatus = true; |
| | | private InternalClientConnection conn = |
| | | private final DN baseDn; |
| | | private final InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | private ASN1OctetString asn1BaseDn; |
| | | private short serverId; |
| | | private final ASN1OctetString asn1BaseDn; |
| | | private final short serverId; |
| | | |
| | | private final ServerState state; |
| | | |
| | | /** |
| | | * The attribute name used to store the state in the backend. |
| | |
| | | /** |
| | | * create a new ServerState. |
| | | * @param baseDn The baseDN for which the ServerState is created |
| | | * @param serverId The serverId |
| | | * @param serverId The serverId |
| | | */ |
| | | public PersistentServerState(DN baseDn, short serverId) |
| | | { |
| | | this.baseDn = baseDn; |
| | | this.serverId = serverId; |
| | | this.state = new ServerState(); |
| | | asn1BaseDn = new ASN1OctetString(baseDn.toString()); |
| | | loadState(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | * Create a new PersistenServerState based on an already existing ServerState. |
| | | * |
| | | * @param baseDn The baseDN for which the ServerState is created. |
| | | * @param serverId The serverId. |
| | | * @param state The serverState. |
| | | */ |
| | | @Override |
| | | public PersistentServerState(DN baseDn, short serverId, ServerState state) |
| | | { |
| | | this.baseDn = baseDn; |
| | | this.serverId = serverId; |
| | | this.state = state; |
| | | asn1BaseDn = new ASN1OctetString(baseDn.toString()); |
| | | loadState(); |
| | | } |
| | | |
| | | /** |
| | | * Update the Server State with a ChangeNumber. |
| | | * All operations with smaller CSN and the same serverID must be committed |
| | | * before calling this method. |
| | | * |
| | | * @param changeNumber The committed ChangeNumber. |
| | | * |
| | | * @return a boolean indicating if the update was meaningful. |
| | | */ |
| | | public boolean update(ChangeNumber changeNumber) |
| | | { |
| | | savedStatus = false; |
| | | return super.update(changeNumber); |
| | | return state.update(changeNumber); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public void save() |
| | | { |
| | | if (savedStatus) |
| | | if (state.isSaved()) |
| | | return; |
| | | |
| | | savedStatus = true; |
| | | state.setSaved(true); |
| | | ResultCode resultCode = updateStateEntry(); |
| | | if (resultCode != ResultCode.SUCCESS) |
| | | { |
| | | savedStatus = false; |
| | | state.setSaved(false); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | private ResultCode runUpdateStateEntry(DN serverStateEntryDN) |
| | | { |
| | | ArrayList<ASN1OctetString> values = this.toASN1ArrayList(); |
| | | ArrayList<ASN1OctetString> values = state.toASN1ArrayList(); |
| | | |
| | | LDAPAttribute attr = |
| | | new LDAPAttribute(REPLICATION_STATE, values); |
| | |
| | | */ |
| | | public void clearInMemory() |
| | | { |
| | | super.clear(); |
| | | this.savedStatus = false; |
| | | state.clear(); |
| | | state.setSaved(false); |
| | | } |
| | | |
| | | /** |
| | |
| | | // maxCn stored in the serverState |
| | | synchronized (this) |
| | | { |
| | | serverStateMaxCn = this.getMaxChangeNumber(serverId); |
| | | serverStateMaxCn = state.getMaxChangeNumber(serverId); |
| | | |
| | | if (serverStateMaxCn == null) |
| | | return; |
| | | |
| | | try { |
| | | op = ReplicationBroker.searchForChangedEntries(baseDn, |
| | | op = LDAPReplicationDomain.searchForChangedEntries(baseDn, |
| | | serverStateMaxCn, null); |
| | | } |
| | | catch (Exception e) |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the largest ChangeNumber seen for a given LDAP server ID. |
| | | * |
| | | * @param serverID The server ID. |
| | | * |
| | | * @return The largest ChangeNumber seen. |
| | | */ |
| | | public ChangeNumber getMaxChangeNumber(short serverID) |
| | | { |
| | | return state.getMaxChangeNumber(serverID); |
| | | } |
| | | } |
| | |
| | | import org.opends.server.core.ModifyDNOperationBasis; |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.DeleteMsg; |
| | | import org.opends.server.replication.protocol.ModifyDNMsg; |
| | | import org.opends.server.replication.protocol.OperationContext; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Operation; |
| | | |
| | |
| | | new TreeSet<PendingChange>(); |
| | | |
| | | /** |
| | | * The ServerState that will be updated when UpdateMsg are fully replayed. |
| | | * The ServerState that will be updated when LDAPUpdateMsg are fully replayed. |
| | | */ |
| | | private ServerState state; |
| | | |
| | | /** |
| | | * The ChangeNumberGenerator to must be adjusted when new changes |
| | | * are received from a remote server. |
| | | */ |
| | | private ChangeNumberGenerator changeNumberGenerator; |
| | | |
| | | /** |
| | | * Creates a new RemotePendingChanges using the provided ServerState. |
| | | * |
| | | * @param changeNumberGenerator The ChangeNumberGenerator that should |
| | | * be adjusted when changes are received. |
| | | * @param state The ServerState that will be updated when UpdateMsg |
| | | * @param state The ServerState that will be updated when LDAPUpdateMsg |
| | | * have been fully replayed. |
| | | */ |
| | | public RemotePendingChanges(ChangeNumberGenerator changeNumberGenerator, |
| | | ServerState state) |
| | | public RemotePendingChanges(ServerState state) |
| | | { |
| | | this.changeNumberGenerator = changeNumberGenerator; |
| | | this.state = state; |
| | | } |
| | | |
| | | /** |
| | | * Add a new UpdateMsg that was received from the replication server |
| | | * Add a new LDAPUpdateMsg that was received from the replication server |
| | | * to the pendingList. |
| | | * |
| | | * @param update The UpdateMsg that was received from the replication |
| | | * @param update The LDAPUpdateMsg that was received from the replication |
| | | * server and that will be added to the pending list. |
| | | */ |
| | | public synchronized void putRemoteUpdate(UpdateMsg update) |
| | | public synchronized void putRemoteUpdate(LDAPUpdateMsg update) |
| | | { |
| | | ChangeNumber changeNumber = update.getChangeNumber(); |
| | | changeNumberGenerator.adjust(changeNumber); |
| | | pendingChanges.put(changeNumber, new PendingChange(changeNumber, null, |
| | | update)); |
| | | } |
| | |
| | | /** |
| | | * Get the first update in the list that have some dependencies cleared. |
| | | * |
| | | * @return The UpdateMsg to be handled. |
| | | * @return The LDAPUpdateMsg to be handled. |
| | | */ |
| | | public synchronized UpdateMsg getNextUpdate() |
| | | public synchronized LDAPUpdateMsg getNextUpdate() |
| | | { |
| | | /* |
| | | * Parse the list of Update with dependencies and check if the dependencies |
| | |
| | | { |
| | | if (pendingChange.getChangeNumber().older(changeNumber)) |
| | | { |
| | | UpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | LDAPUpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | if (pendingMsg != null) |
| | | { |
| | | if (pendingMsg instanceof DeleteMsg) |
| | |
| | | { |
| | | if (pendingChange.getChangeNumber().older(changeNumber)) |
| | | { |
| | | UpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | LDAPUpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | if (pendingMsg != null) |
| | | { |
| | | if (pendingMsg instanceof AddMsg) |
| | |
| | | { |
| | | if (pendingChange.getChangeNumber().older(changeNumber)) |
| | | { |
| | | UpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | LDAPUpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | if (pendingMsg != null) |
| | | { |
| | | if (pendingMsg instanceof DeleteMsg) |
| | |
| | | { |
| | | if (pendingChange.getChangeNumber().older(changeNumber)) |
| | | { |
| | | UpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | LDAPUpdateMsg pendingMsg = pendingChange.getMsg(); |
| | | if (pendingMsg != null) |
| | | { |
| | | if (pendingMsg instanceof DeleteMsg) |
| | |
| | | * @return A boolean indicating if an operation cannot be replayed |
| | | * because of dependencies. |
| | | */ |
| | | public boolean checkDependencies(Operation op, UpdateMsg msg) |
| | | public boolean checkDependencies(Operation op, LDAPUpdateMsg msg) |
| | | { |
| | | if (op instanceof ModifyOperation) |
| | | { |
| | |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | |
| | | import org.opends.server.replication.service.ReplicationDomain; |
| | | import org.opends.server.util.ServerConstants; |
| | | |
| | | /** |
| | |
| | | // of entries to export. |
| | | throw(new IOException()); |
| | | } |
| | | if (numEntries<0) |
| | | domain.exportLDIFEntry(entryBuffer); |
| | | |
| | | numExportedEntries++; |
| | | entryBuffer = ""; |
| | | |
| | |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | |
| | | import java.util.concurrent.BlockingQueue; |
| | | import java.util.concurrent.TimeUnit; |
| | | import org.opends.messages.Message; |
| | |
| | | |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | |
| | | /** |
| | | * Thread that is used to get message from the replication servers (stored |
| | |
| | | TimeUnit.SECONDS)) != null)) |
| | | { |
| | | // Find replication domain for that update message |
| | | UpdateMsg updateMsg = updateToreplay.getUpdateMessage(); |
| | | ReplicationDomain domain = updateToreplay.getReplicationDomain(); |
| | | LDAPUpdateMsg updateMsg = updateToreplay.getUpdateMessage(); |
| | | LDAPReplicationDomain domain = updateToreplay.getReplicationDomain(); |
| | | domain.replay(updateMsg); |
| | | } |
| | | } catch (Exception e) |
| | |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | |
| | | /** |
| | | * This is a bag class to hold an update to replay in the queue of updates to |
| | |
| | | */ |
| | | public class UpdateToReplay |
| | | { |
| | | private UpdateMsg updateMessage = null; |
| | | private ReplicationDomain replicationDomain = null; |
| | | private LDAPUpdateMsg updateMessage = null; |
| | | private LDAPReplicationDomain replicationDomain = null; |
| | | |
| | | /** |
| | | * Construct the object associating the update message with the replication |
| | |
| | | * @param replicationDomain The replication domain to use for replaying the |
| | | * change from the update message |
| | | */ |
| | | public UpdateToReplay(UpdateMsg updateMessage, |
| | | ReplicationDomain replicationDomain) |
| | | public UpdateToReplay(LDAPUpdateMsg updateMessage, |
| | | LDAPReplicationDomain replicationDomain) |
| | | { |
| | | this.updateMessage = updateMessage; |
| | | this.replicationDomain = replicationDomain; |
| | |
| | | * Getter for update message. |
| | | * @return The update message |
| | | */ |
| | | public UpdateMsg getUpdateMessage() |
| | | public LDAPUpdateMsg getUpdateMessage() |
| | | { |
| | | return updateMessage; |
| | | } |
| | |
| | | * Getter for replication domain. |
| | | * @return The replication domain |
| | | */ |
| | | public ReplicationDomain getReplicationDomain() |
| | | public LDAPReplicationDomain getReplicationDomain() |
| | | { |
| | | return replicationDomain; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Sets the timeout marker for this message. |
| | | * @param hasTimeout True if some timeout occurred |
| | | */ |
| | | public void setHasTimeout(boolean hasTimeout) |
| | | { |
| | | this.hasTimeout = hasTimeout; |
| | | } |
| | | |
| | | /** |
| | | * Sets the wrong status marker for this message. |
| | | * @param hasWrongStatus True if some servers were in wrong status |
| | | */ |
| | | public void setHasWrongStatus(boolean hasWrongStatus) |
| | | { |
| | | this.hasWrongStatus = hasWrongStatus; |
| | | } |
| | | |
| | | /** |
| | | * Sets the replay error marker for this message. |
| | | * @param hasReplayError True if some servers had errors replaying the change |
| | | */ |
| | | public void setHasReplayError(boolean hasReplayError) |
| | | { |
| | | this.hasReplayError = hasReplayError; |
| | | } |
| | | |
| | | /** |
| | | * Sets the list of failing servers for this message. |
| | | * @param failedServers The list of failing servers for this message. |
| | | */ |
| | | public void setFailedServers(List<Short> failedServers) |
| | | { |
| | | this.failedServers = failedServers; |
| | | } |
| | | |
| | | /** |
| | | * Creates a new AckMsg by decoding the provided byte array. |
| | | * |
| | | * @param in The byte array containing the encoded form of the AckMsg. |
| | |
| | | return failedServers; |
| | | } |
| | | |
| | | /** |
| | | * Transforms the errors information of the ack into human readable string. |
| | | * @return A human readable string for errors embedded in the message. |
| | | */ |
| | | public String errorsToString() |
| | | { |
| | | String idList = null; |
| | | if (failedServers.size() > 0) |
| | | { |
| | | idList = "["; |
| | | int size = failedServers.size(); |
| | | for (int i=0 ; i<size ; i++) { |
| | | idList += failedServers.get(i); |
| | | if ( i != (size-1) ) |
| | | idList += ", "; |
| | | } |
| | | idList += "]"; |
| | | } else |
| | | { |
| | | idList="none"; |
| | | } |
| | | |
| | | String ackErrorStr = "hasTimeout: " + (hasTimeout ? "yes" : "no") + ", " + |
| | | "hasWrongStatus: " + (hasWrongStatus ? "yes" : "no") + ", " + |
| | | "hasReplayError: " + (hasReplayError ? "yes" : "no") + ", " + |
| | | " concerned server ids: " + idList; |
| | | |
| | | return ackErrorStr; |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | * This class is used to exchange Add operation between LDAP servers |
| | | * and replication servers. |
| | | */ |
| | | public class AddMsg extends UpdateMsg |
| | | public class AddMsg extends LDAPUpdateMsg |
| | | { |
| | | private byte[] encodedAttributes; |
| | | private String parentUniqueId; |
| | |
| | | /** |
| | | * Object used when sending delete information to replication servers. |
| | | */ |
| | | public class DeleteMsg extends UpdateMsg |
| | | public class DeleteMsg extends LDAPUpdateMsg |
| | | { |
| | | /** |
| | | * Creates a new delete message. |
| | |
| | | /** |
| | | * Creates a new EntryMsg. |
| | | * |
| | | * @param sender The sender of this message. |
| | | * @param destination The destination of this message. |
| | | * @param entryBytes The bytes of the entry. |
| | | */ |
| | | public EntryMsg( |
| | | short sender, |
| | | short destination, |
| | | byte[] entryBytes) |
| | | { |
| | | super(sender, destination); |
| | | this.entryByteArray = new byte[entryBytes.length]; |
| | | System.arraycopy(entryBytes, 0, this.entryByteArray, 0, entryBytes.length); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new EntryMsg. |
| | | * |
| | | * @param sender The sender of this message. |
| | | * @param destination The destination of this message. |
| | | * @param entryBytes The bytes of the entry. |
| | | * @param pos The starting Position in the array. |
| | | * @param length Number of array elements to be copied. |
| | | */ |
| | | public EntryMsg(short sender, short destination, byte[] entryBytes) |
| | | public EntryMsg( |
| | | short sender, |
| | | short destination, |
| | | byte[] entryBytes, |
| | | int pos, |
| | | int length) |
| | | { |
| | | super(sender, destination); |
| | | this.entryByteArray = entryBytes.clone(); |
| | | this.entryByteArray = new byte[length]; |
| | | System.arraycopy(entryBytes, pos, this.entryByteArray, 0, length); |
| | | } |
| | | |
| | | /** |
| File was renamed from opends/src/server/org/opends/server/replication/plugin/HeartbeatMonitor.java |
| | |
| | | * Copyright 2007-2008 Sun Microsystems, Inc. |
| | | */ |
| | | |
| | | package org.opends.server.replication.plugin; |
| | | package org.opends.server.replication.protocol; |
| | | |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | |
| | | import java.io.IOException; |
| | | |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.replication.protocol.ProtocolSession; |
| | | |
| | | /** |
| | | * This class implements a thread to monitor heartbeat messages from the |
| | |
| | | * @param destination destination of this message |
| | | * @param senderID serverID of the server that will send this message |
| | | */ |
| | | public InitializeRequestMsg(DN baseDn, short senderID, short destination) |
| | | public InitializeRequestMsg(String baseDn, short senderID, short destination) |
| | | { |
| | | super(senderID, destination); |
| | | this.baseDn = baseDn.toNormalizedString(); |
| | | this.baseDn = baseDn; |
| | | } |
| | | |
| | | /** |
| | |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.util.zip.DataFormatException; |
| | | |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | |
| | | /** |
| | | * This message is part of the replication protocol. |
| | | * This message is sent by a server to one or several servers as the |
| | |
| | | * @param requestorID The server that initiates this export. |
| | | * @param entryCount The count of entries that will be sent. |
| | | */ |
| | | public InitializeTargetMsg(DN baseDN, short senderID, |
| | | public InitializeTargetMsg(String baseDN, short senderID, |
| | | short destination, short requestorID, long entryCount) |
| | | { |
| | | super(senderID, destination); |
| | | this.requestorID = requestorID; |
| | | this.baseDN = baseDN.toNormalizedString(); |
| | | this.baseDN = baseDN; |
| | | this.entryCount = entryCount; |
| | | } |
| | | |
| | |
| | | * |
| | | * @return the base DN |
| | | */ |
| | | public DN getBaseDN() |
| | | public String getBaseDN() |
| | | { |
| | | if (baseDN == null) |
| | | return null; |
| | | try |
| | | { |
| | | return DN.decode(baseDN); |
| | | } catch (DirectoryException e) |
| | | { |
| | | return null; |
| | | } |
| | | return baseDN; |
| | | } |
| | | |
| | | /** |
| New file |
| | |
| | | /* |
| | | * 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-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.protocol; |
| | | |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.util.zip.DataFormatException; |
| | | |
| | | import org.opends.server.protocols.asn1.ASN1Exception; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.replication.common.AssuredMode; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.types.AbstractOperation; |
| | | import org.opends.server.types.LDAPException; |
| | | import org.opends.server.types.operation.PostOperationAddOperation; |
| | | import org.opends.server.types.operation.PostOperationDeleteOperation; |
| | | import org.opends.server.types.operation.PostOperationModifyDNOperation; |
| | | import org.opends.server.types.operation.PostOperationModifyOperation; |
| | | import org.opends.server.types.operation.PostOperationOperation; |
| | | |
| | | /** |
| | | * Abstract class that must be extended to define a message |
| | | * used for sending Updates between servers. |
| | | */ |
| | | public abstract class LDAPUpdateMsg extends UpdateMsg |
| | | { |
| | | /** |
| | | * The DN on which the update was originally done. |
| | | */ |
| | | protected String dn = null; |
| | | |
| | | /** |
| | | * The uniqueId of the entry that was updated. |
| | | */ |
| | | protected String uniqueId; |
| | | |
| | | /** |
| | | * Creates a new UpdateMsg. |
| | | */ |
| | | public LDAPUpdateMsg() |
| | | { |
| | | } |
| | | |
| | | /** |
| | | * Creates a new UpdateMsg with the given informations. |
| | | * |
| | | * @param ctx The replication Context of the operation for which the |
| | | * update message must be created,. |
| | | * @param dn The DN of the entry on which the change |
| | | * that caused the creation of this object happened |
| | | */ |
| | | public LDAPUpdateMsg(OperationContext ctx, String dn) |
| | | { |
| | | this.protocolVersion = ProtocolVersion.getCurrentVersion(); |
| | | this.changeNumber = ctx.getChangeNumber(); |
| | | this.uniqueId = ctx.getEntryUid(); |
| | | this.dn = dn; |
| | | } |
| | | |
| | | /** |
| | | * Creates a new UpdateMessage with the given informations. |
| | | * |
| | | * @param cn The ChangeNumber of the operation for which the |
| | | * UpdateMessage is created. |
| | | * @param entryUUID The Unique identifier of the entry that is updated |
| | | * by the operation for which the UpdateMessage is created. |
| | | * @param dn The DN of the entry on which the change |
| | | * that caused the creation of this object happened |
| | | */ |
| | | public LDAPUpdateMsg(ChangeNumber cn, String entryUUID, String dn) |
| | | { |
| | | this.protocolVersion = ProtocolVersion.getCurrentVersion(); |
| | | this.changeNumber = cn; |
| | | this.uniqueId = entryUUID; |
| | | this.dn = dn; |
| | | } |
| | | |
| | | /** |
| | | * Generates an Update Message with the provided information. |
| | | * |
| | | * @param op The operation for which the message must be created. |
| | | * @return The generated message. |
| | | */ |
| | | public static LDAPUpdateMsg generateMsg(PostOperationOperation op) |
| | | { |
| | | LDAPUpdateMsg msg = null; |
| | | switch (op.getOperationType()) |
| | | { |
| | | case MODIFY : |
| | | msg = new ModifyMsg((PostOperationModifyOperation) op); |
| | | break; |
| | | |
| | | case ADD: |
| | | msg = new AddMsg((PostOperationAddOperation) op); |
| | | break; |
| | | |
| | | case DELETE : |
| | | msg = new DeleteMsg((PostOperationDeleteOperation) op); |
| | | break; |
| | | |
| | | case MODIFY_DN : |
| | | msg = new ModifyDNMsg( (PostOperationModifyDNOperation) op); |
| | | break; |
| | | } |
| | | |
| | | return msg; |
| | | } |
| | | |
| | | /** |
| | | * Get the DN on which the operation happened. |
| | | * |
| | | * @return The DN on which the operations happened. |
| | | */ |
| | | public String getDn() |
| | | { |
| | | return dn; |
| | | } |
| | | |
| | | /** |
| | | * Set the DN. |
| | | * @param dn The dn that must now be used for this message. |
| | | */ |
| | | public void setDn(String dn) |
| | | { |
| | | this.dn = dn; |
| | | } |
| | | |
| | | /** |
| | | * Get the Unique Identifier of the entry on which the operation happened. |
| | | * |
| | | * @return The Unique Identifier of the entry on which the operation happened. |
| | | */ |
| | | public String getUniqueId() |
| | | { |
| | | return uniqueId; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Create and Operation from the message. |
| | | * |
| | | * @param conn connection to use when creating the message |
| | | * @return the created Operation |
| | | * @throws LDAPException In case of LDAP decoding exception. |
| | | * @throws ASN1Exception In case of ASN1 decoding exception. |
| | | * @throws DataFormatException In case of bad msg format. |
| | | */ |
| | | public AbstractOperation createOperation(InternalClientConnection conn) |
| | | throws LDAPException, ASN1Exception, DataFormatException |
| | | { |
| | | return createOperation(conn, dn); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Create and Operation from the message using the provided DN. |
| | | * |
| | | * @param conn connection to use when creating the message. |
| | | * @param newDn the DN to use when creating the operation. |
| | | * @return the created Operation. |
| | | * @throws LDAPException In case of LDAP decoding exception. |
| | | * @throws ASN1Exception In case of ASN1 decoding exception. |
| | | * @throws DataFormatException In case of bad msg format. |
| | | */ |
| | | public abstract AbstractOperation createOperation( |
| | | InternalClientConnection conn, String newDn) |
| | | throws LDAPException, ASN1Exception, DataFormatException; |
| | | |
| | | /** |
| | | * Encode the common header for all the UpdateMsg. This uses the current |
| | | * protocol version. |
| | | * |
| | | * @param type the type of UpdateMsg to encode. |
| | | * @param additionalLength additional length needed to encode the remaining |
| | | * part of the UpdateMsg. |
| | | * @return a byte array containing the common header and enough space to |
| | | * encode the remaining bytes of the UpdateMsg as was specified |
| | | * by the additionalLength. |
| | | * (byte array length = common header length + additionalLength) |
| | | * @throws UnsupportedEncodingException if UTF-8 is not supported. |
| | | */ |
| | | public byte[] encodeHeader(byte type, int additionalLength) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | byte[] byteDn = dn.getBytes("UTF-8"); |
| | | byte[] changeNumberByte = |
| | | this.getChangeNumber().toString().getBytes("UTF-8"); |
| | | byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8"); |
| | | |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><dn><entryuuid><assured> |
| | | * <assured mode> <safe data level> |
| | | * the length of result byte array is therefore : |
| | | * 1 + 1 + change number length + 1 + dn length + 1 + uuid length + 1 + 1 |
| | | * + 1 + 1 + additional_length |
| | | */ |
| | | int length = 8 + changeNumberByte.length + byteDn.length |
| | | + byteEntryuuid.length + additionalLength; |
| | | |
| | | byte[] encodedMsg = new byte[length]; |
| | | |
| | | /* put the type of the operation */ |
| | | encodedMsg[0] = type; |
| | | |
| | | /* put the protocol version */ |
| | | encodedMsg[1] = (byte)ProtocolVersion.getCurrentVersion(); |
| | | int pos = 2; |
| | | |
| | | /* Put the ChangeNumber */ |
| | | pos = addByteArray(changeNumberByte, encodedMsg, pos); |
| | | |
| | | /* Put the DN and a terminating 0 */ |
| | | pos = addByteArray(byteDn, encodedMsg, pos); |
| | | |
| | | /* Put the entry uuid and a terminating 0 */ |
| | | pos = addByteArray(byteEntryuuid, encodedMsg, pos); |
| | | |
| | | /* Put the assured flag */ |
| | | encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0); |
| | | |
| | | /* Put the assured mode */ |
| | | encodedMsg[pos++] = assuredMode.getValue(); |
| | | |
| | | /* Put the safe data level */ |
| | | encodedMsg[pos++] = safeDataLevel; |
| | | |
| | | return encodedMsg; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public byte[] getBytes(short reqProtocolVersion) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | |
| | | // Using current protocol version should normally not be done as we would |
| | | // normally call the getBytes() method instead for that. So this check |
| | | // for security |
| | | if (reqProtocolVersion == ProtocolVersion.getCurrentVersion()) |
| | | { |
| | | return getBytes(); |
| | | } |
| | | |
| | | switch (reqProtocolVersion) |
| | | { |
| | | case ProtocolVersion.REPLICATION_PROTOCOL_V1: |
| | | return getBytes_V1(); |
| | | default: |
| | | // Unsupported requested version |
| | | throw new UnsupportedEncodingException(getClass().getSimpleName() + |
| | | " PDU does not support requested protocol version serialization: " + |
| | | reqProtocolVersion); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the byte array representation of this Message. This uses the version |
| | | * 1 of the replication protocol (used for compatibility purpose). |
| | | * |
| | | * @return The byte array representation of this Message. |
| | | * |
| | | * @throws UnsupportedEncodingException When the encoding of the message |
| | | * failed because the UTF-8 encoding is not supported. |
| | | */ |
| | | public abstract byte[] getBytes_V1() throws UnsupportedEncodingException; |
| | | |
| | | /** |
| | | * Encode the common header for all the UpdateMessage. This uses the version |
| | | * 1 of the replication protocol (used for compatibility purpose). |
| | | * |
| | | * @param type the type of UpdateMessage to encode. |
| | | * @param additionalLength additional length needed to encode the remaining |
| | | * part of the UpdateMessage. |
| | | * @return a byte array containing the common header and enough space to |
| | | * encode the remaining bytes of the UpdateMessage as was specified |
| | | * by the additionalLength. |
| | | * (byte array length = common header length + additionalLength) |
| | | * @throws UnsupportedEncodingException if UTF-8 is not supported. |
| | | */ |
| | | public byte[] encodeHeader_V1(byte type, int additionalLength) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | byte[] byteDn = dn.getBytes("UTF-8"); |
| | | byte[] changeNumberByte = |
| | | this.getChangeNumber().toString().getBytes("UTF-8"); |
| | | byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8"); |
| | | |
| | | /* The message header is stored in the form : |
| | | * <operation type><changenumber><dn><assured><entryuuid><change> |
| | | * the length of result byte array is therefore : |
| | | * 1 + change number length + 1 + dn length + 1 + 1 + |
| | | * uuid length + 1 + additional_length |
| | | */ |
| | | int length = 5 + changeNumberByte.length + byteDn.length |
| | | + byteEntryuuid.length + additionalLength; |
| | | |
| | | byte[] encodedMsg = new byte[length]; |
| | | |
| | | /* put the type of the operation */ |
| | | encodedMsg[0] = type; |
| | | int pos = 1; |
| | | |
| | | /* put the ChangeNumber */ |
| | | pos = addByteArray(changeNumberByte, encodedMsg, pos); |
| | | |
| | | /* put the assured information */ |
| | | encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0); |
| | | |
| | | /* put the DN and a terminating 0 */ |
| | | pos = addByteArray(byteDn, encodedMsg, pos); |
| | | |
| | | /* put the entry uuid and a terminating 0 */ |
| | | pos = addByteArray(byteEntryuuid, encodedMsg, pos); |
| | | |
| | | return encodedMsg; |
| | | } |
| | | |
| | | /** |
| | | * Decode the Header part of this Update Message, and check its type. |
| | | * |
| | | * @param types The allowed types of this Update Message. |
| | | * @param encodedMsg the encoded form of the UpdateMsg. |
| | | * @return the position at which the remaining part of the message starts. |
| | | * @throws DataFormatException if the encodedMsg does not contain a valid |
| | | * common header. |
| | | */ |
| | | public int decodeHeader(byte[] types, byte[] encodedMsg) |
| | | throws DataFormatException |
| | | { |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><dn><entryuuid><assured> |
| | | * <assured mode> <safe data level> |
| | | */ |
| | | |
| | | /* first byte is the type */ |
| | | boolean foundMatchingType = false; |
| | | for (int i = 0; i < types.length; i++) |
| | | { |
| | | if (types[i] == encodedMsg[0]) |
| | | { |
| | | foundMatchingType = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!foundMatchingType) |
| | | throw new DataFormatException("byte[] is not a valid update msg: " |
| | | + encodedMsg[0]); |
| | | |
| | | /* |
| | | * For older protocol version PDUs, decode the matching version header |
| | | * instead. |
| | | */ |
| | | if ((encodedMsg[0] == MSG_TYPE_ADD_V1) || |
| | | (encodedMsg[0] == MSG_TYPE_DELETE_V1) || |
| | | (encodedMsg[0] == MSG_TYPE_MODIFYDN_V1) || |
| | | (encodedMsg[0] == MSG_TYPE_MODIFY_V1)) |
| | | { |
| | | return decodeHeader_V1(encodedMsg); |
| | | } |
| | | |
| | | /* read the protocol version */ |
| | | protocolVersion = (short)encodedMsg[1]; |
| | | |
| | | try |
| | | { |
| | | /* Read the changeNumber */ |
| | | int pos = 2; |
| | | int length = getNextLength(encodedMsg, pos); |
| | | String changenumberStr = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | changeNumber = new ChangeNumber(changenumberStr); |
| | | |
| | | /* Read the dn */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | dn = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | /* Read the entryuuid */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | uniqueId = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | /* Read the assured information */ |
| | | if (encodedMsg[pos++] == 1) |
| | | assuredFlag = true; |
| | | else |
| | | assuredFlag = false; |
| | | |
| | | /* Read the assured mode */ |
| | | assuredMode = AssuredMode.valueOf(encodedMsg[pos++]); |
| | | |
| | | /* Read the safe data level */ |
| | | safeDataLevel = encodedMsg[pos++]; |
| | | |
| | | return pos; |
| | | } catch (UnsupportedEncodingException e) |
| | | { |
| | | throw new DataFormatException("UTF-8 is not supported by this jvm."); |
| | | } catch (IllegalArgumentException e) |
| | | { |
| | | throw new DataFormatException(e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Decode the Header part of this Update Message, and check its type. This |
| | | * uses the version 1 of the replication protocol (used for compatibility |
| | | * purpose). |
| | | * |
| | | * @param encodedMsg the encoded form of the UpdateMessage. |
| | | * @return the position at which the remaining part of the message starts. |
| | | * @throws DataFormatException if the encodedMsg does not contain a valid |
| | | * common header. |
| | | */ |
| | | public int decodeHeader_V1(byte[] encodedMsg) |
| | | throws DataFormatException |
| | | { |
| | | if ((encodedMsg[0] != MSG_TYPE_ADD_V1) && |
| | | (encodedMsg[0] != MSG_TYPE_DELETE_V1) && |
| | | (encodedMsg[0] != MSG_TYPE_MODIFYDN_V1) && |
| | | (encodedMsg[0] != MSG_TYPE_MODIFY_V1)) |
| | | throw new DataFormatException("byte[] is not a valid update msg: expected" |
| | | + " a V1 PDU, received: " + encodedMsg[0]); |
| | | |
| | | // Force version to V1 (other new parameters take their default values |
| | | // (assured stuff...)) |
| | | protocolVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1; |
| | | |
| | | try |
| | | { |
| | | /* read the changeNumber */ |
| | | int pos = 1; |
| | | int length = getNextLength(encodedMsg, pos); |
| | | String changenumberStr = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | changeNumber = new ChangeNumber(changenumberStr); |
| | | |
| | | /* read the assured information */ |
| | | if (encodedMsg[pos++] == 1) |
| | | assuredFlag = true; |
| | | else |
| | | assuredFlag = false; |
| | | |
| | | /* read the dn */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | dn = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | /* read the entryuuid */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | uniqueId = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | return pos; |
| | | } catch (UnsupportedEncodingException e) |
| | | { |
| | | throw new DataFormatException("UTF-8 is not supported by this jvm."); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Return the number of bytes used by this message. |
| | | * |
| | | * @return The number of bytes used by this message. |
| | | */ |
| | | public abstract int size(); |
| | | } |
| | |
| | | /** |
| | | * This class holds every common code for the modify messages (mod, moddn). |
| | | */ |
| | | public abstract class ModifyCommonMsg extends UpdateMsg { |
| | | public abstract class ModifyCommonMsg extends LDAPUpdateMsg { |
| | | |
| | | /** |
| | | * The modifications kept encoded in the message. |
| | |
| | | import java.util.zip.DataFormatException; |
| | | |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.DN; |
| | | |
| | | /** |
| | | * Message sent by a replication server to another replication server |
| | |
| | | * @param groupId The group id of the RS |
| | | * @param degradedStatusThreshold The degraded status threshold |
| | | */ |
| | | public ReplServerStartMsg(short serverId, String serverURL, DN baseDn, |
| | | public ReplServerStartMsg(short serverId, String serverURL, String baseDn, |
| | | int windowSize, |
| | | ServerState serverState, |
| | | short protocolVersion, |
| | |
| | | this.serverId = serverId; |
| | | this.serverURL = serverURL; |
| | | if (baseDn != null) |
| | | this.baseDn = baseDn.toNormalizedString(); |
| | | this.baseDn = baseDn; |
| | | else |
| | | this.baseDn = null; |
| | | this.windowSize = windowSize; |
| | |
| | | * |
| | | * @return the base DN from this ReplServerStartMsg. |
| | | */ |
| | | public DN getBaseDn() |
| | | public String getBaseDn() |
| | | { |
| | | if (baseDn == null) |
| | | return null; |
| | | try |
| | | { |
| | | return DN.decode(baseDn); |
| | | } catch (DirectoryException e) |
| | | { |
| | | return null; |
| | | } |
| | | return baseDn; |
| | | } |
| | | |
| | | /** |
| | |
| | | return "ReplServerStartMsg content: " + |
| | | "\nprotocolVersion: " + protocolVersion + |
| | | "\ngenerationId: " + generationId + |
| | | "\nbaseDn: " + baseDn + |
| | | "\ngroupId: " + groupId + |
| | | "\nbaseDn: " + baseDn.toString() + |
| | | "\nserverId: " + serverId + |
| | | "\nserverState: " + serverState + |
| | | "\nserverURL: " + serverURL + |
| | |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.admin.std.server.ReplicationServerCfg; |
| | | import org.opends.server.admin.std.server.ReplicationDomainCfg; |
| | | import org.opends.server.types.DirectoryConfig; |
| | | import org.opends.server.types.CryptoManager; |
| | | import org.opends.server.config.ConfigException; |
| | |
| | | } |
| | | |
| | | /** |
| | | * Create a ReplSessionSecurity instance from a provided replication server |
| | | * configuration. |
| | | * |
| | | * @param replServerCfg The replication server configuration. |
| | | * |
| | | * @throws ConfigException If the supplied configuration was not valid. |
| | | */ |
| | | public ReplSessionSecurity(ReplicationServerCfg replServerCfg) |
| | | throws ConfigException |
| | | { |
| | | // Currently use global settings from the crypto manager. |
| | | this(DirectoryConfig.getCryptoManager().getSslCertNickname(), |
| | | DirectoryConfig.getCryptoManager().getSslProtocols(), |
| | | DirectoryConfig.getCryptoManager().getSslCipherSuites(), |
| | | DirectoryConfig.getCryptoManager().isSslEncryption()); |
| | | } |
| | | |
| | | /** |
| | | * Create a ReplSessionSecurity instance from a provided multimaster domain |
| | | * configuration. |
| | | * |
| | | * @param multimasterDomainCfg The multimaster domain configuration. |
| | | * |
| | | * @throws ConfigException If the supplied configuration was not valid. |
| | | */ |
| | | public ReplSessionSecurity(ReplicationDomainCfg multimasterDomainCfg) |
| | | public ReplSessionSecurity() |
| | | throws ConfigException |
| | | { |
| | | // Currently use global settings from the crypto manager. |
| | |
| | | static final byte MSG_TYPE_TOPOLOGY = 26; |
| | | static final byte MSG_TYPE_START_SESSION = 27; |
| | | static final byte MSG_TYPE_CHANGE_STATUS = 28; |
| | | static final byte MSG_TYPE_GENERIC_UPDATE = 29; |
| | | |
| | | // Adding a new type of message here probably requires to |
| | | // change accordingly generateMsg method below |
| | |
| | | case MSG_TYPE_CHANGE_STATUS: |
| | | msg = new ChangeStatusMsg(buffer); |
| | | break; |
| | | case MSG_TYPE_GENERIC_UPDATE: |
| | | msg = new UpdateMsg(buffer); |
| | | break; |
| | | default: |
| | | throw new DataFormatException("received message with unknown type"); |
| | | } |
| | |
| | | * @param pos the position where to concatenate. |
| | | * @return the next position to use in the resultByteArray. |
| | | */ |
| | | protected int addByteArray(byte[] tail, byte[] resultByteArray, int pos) |
| | | protected static int addByteArray(byte[] tail, byte[] resultByteArray, |
| | | int pos) |
| | | { |
| | | for (int i=0; i<tail.length; i++,pos++) |
| | | { |
| | |
| | | import java.util.zip.DataFormatException; |
| | | |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | |
| | | /** |
| | | * This message is used by LDAP server when they first connect. |
| | |
| | | * after the start messages have been exchanged. |
| | | * @param groupId The group id of the DS for this DN |
| | | */ |
| | | public ServerStartMsg(short serverId, DN baseDn, int maxReceiveDelay, |
| | | public ServerStartMsg(short serverId, String baseDn, int maxReceiveDelay, |
| | | int maxReceiveQueue, int maxSendDelay, |
| | | int maxSendQueue, int windowSize, |
| | | long heartbeatInterval, |
| | |
| | | super(protocolVersion, generationId); |
| | | |
| | | this.serverId = serverId; |
| | | this.baseDn = baseDn.toString(); |
| | | this.baseDn = baseDn; |
| | | this.maxReceiveDelay = maxReceiveDelay; |
| | | this.maxReceiveQueue = maxReceiveQueue; |
| | | this.maxSendDelay = maxSendDelay; |
| | |
| | | * Get the baseDn. |
| | | * @return Returns the baseDn. |
| | | */ |
| | | public DN getBaseDn() |
| | | public String getBaseDn() |
| | | { |
| | | try |
| | | { |
| | | return DN.decode(baseDn); |
| | | } catch (DirectoryException e) |
| | | { |
| | | return null; |
| | | } |
| | | return baseDn; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | package org.opends.server.replication.protocol; |
| | | |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.util.zip.DataFormatException; |
| | | |
| | | import org.opends.server.protocols.asn1.ASN1Exception; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import java.io.UnsupportedEncodingException; |
| | | import org.opends.server.replication.common.AssuredMode; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.types.AbstractOperation; |
| | | import org.opends.server.types.LDAPException; |
| | | import org.opends.server.types.operation.PostOperationAddOperation; |
| | | import org.opends.server.types.operation.PostOperationDeleteOperation; |
| | | import org.opends.server.types.operation.PostOperationModifyDNOperation; |
| | | import org.opends.server.types.operation.PostOperationModifyOperation; |
| | | import org.opends.server.types.operation.PostOperationOperation; |
| | | |
| | | /** |
| | | * Abstract class that must be extended to define a message |
| | | * used for sending Updates between servers. |
| | | */ |
| | | public abstract class UpdateMsg extends ReplicationMsg |
| | | public class UpdateMsg extends ReplicationMsg |
| | | implements Comparable<UpdateMsg> |
| | | { |
| | | /** |
| | |
| | | protected ChangeNumber changeNumber; |
| | | |
| | | /** |
| | | * The DN on which the update was originally done. |
| | | */ |
| | | protected String dn = null; |
| | | |
| | | /** |
| | | * True when the update must use assured replication. |
| | | */ |
| | | protected boolean assuredFlag = false; |
| | |
| | | /** |
| | | * When assured mode is safe data, gives the requested level. |
| | | */ |
| | | protected byte safeDataLevel = (byte)-1; |
| | | protected byte safeDataLevel = (byte)1; |
| | | |
| | | /** |
| | | * The uniqueId of the entry that was updated. |
| | | * The payload that must be encoded in this message. |
| | | */ |
| | | protected String uniqueId; |
| | | private byte[] payload; |
| | | |
| | | |
| | | /** |
| | | * Creates a new UpdateMsg. |
| | | * Creates a new empty UpdateMsg. |
| | | */ |
| | | public UpdateMsg() |
| | | protected UpdateMsg() |
| | | {} |
| | | |
| | | /** |
| | | * Creates a new UpdateMsg with the given informations. |
| | | * |
| | | * @param bytes A Byte Array with the encoded form of the message. |
| | | * |
| | | * @throws DataFormatException If bytes is not valid. |
| | | */ |
| | | UpdateMsg(byte[] bytes) throws DataFormatException |
| | | { |
| | | // Decode header |
| | | int pos = decodeHeader(MSG_TYPE_GENERIC_UPDATE, bytes); |
| | | |
| | | /* Read the payload : all the remaining bytes but the terminating 0 */ |
| | | int length = bytes.length - pos; |
| | | payload = new byte[length]; |
| | | try |
| | | { |
| | | System.arraycopy(bytes, pos, payload, 0, length); |
| | | } catch (IndexOutOfBoundsException e) |
| | | { |
| | | throw new DataFormatException(e.getMessage()); |
| | | } catch (ArrayStoreException e) |
| | | { |
| | | throw new DataFormatException(e.getMessage()); |
| | | } catch (NullPointerException e) |
| | | { |
| | | throw new DataFormatException(e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Creates a new UpdateMsg with the given informations. |
| | | * |
| | | * @param ctx The replication Context of the operation for which the |
| | | * update message must be created,. |
| | | * @param dn The DN of the entry on which the change |
| | | * that caused the creation of this object happened |
| | | * @param changeNumber The ChangeNumber associated with the change |
| | | * encoded in this message. |
| | | * @param payload The payload that must be encoded in this message. |
| | | */ |
| | | public UpdateMsg(OperationContext ctx, String dn) |
| | | public UpdateMsg(ChangeNumber changeNumber, byte[] payload) |
| | | { |
| | | this.payload = payload; |
| | | this.protocolVersion = ProtocolVersion.getCurrentVersion(); |
| | | this.changeNumber = ctx.getChangeNumber(); |
| | | this.uniqueId = ctx.getEntryUid(); |
| | | this.dn = dn; |
| | | } |
| | | |
| | | /** |
| | | * Creates a new UpdateMessage with the given informations. |
| | | * |
| | | * @param cn The ChangeNumber of the operation for which the |
| | | * UpdateMessage is created. |
| | | * @param entryUUID The Unique identifier of the entry that is updated |
| | | * by the operation for which the UpdateMessage is created. |
| | | * @param dn The DN of the entry on which the change |
| | | * that caused the creation of this object happened |
| | | */ |
| | | public UpdateMsg(ChangeNumber cn, String entryUUID, String dn) |
| | | { |
| | | this.protocolVersion = ProtocolVersion.getCurrentVersion(); |
| | | this.changeNumber = cn; |
| | | this.uniqueId = entryUUID; |
| | | this.dn = dn; |
| | | } |
| | | |
| | | /** |
| | | * Generates an Update Message with the provided information. |
| | | * |
| | | * @param op The operation for which the message must be created. |
| | | * @return The generated message. |
| | | */ |
| | | public static UpdateMsg generateMsg(PostOperationOperation op) |
| | | { |
| | | UpdateMsg msg = null; |
| | | switch (op.getOperationType()) |
| | | { |
| | | case MODIFY : |
| | | msg = new ModifyMsg((PostOperationModifyOperation) op); |
| | | break; |
| | | |
| | | case ADD: |
| | | msg = new AddMsg((PostOperationAddOperation) op); |
| | | break; |
| | | |
| | | case DELETE : |
| | | msg = new DeleteMsg((PostOperationDeleteOperation) op); |
| | | break; |
| | | |
| | | case MODIFY_DN : |
| | | msg = new ModifyDNMsg( (PostOperationModifyDNOperation) op); |
| | | break; |
| | | } |
| | | |
| | | return msg; |
| | | this.changeNumber = changeNumber; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the DN on which the operation happened. |
| | | * |
| | | * @return The DN on which the operations happened. |
| | | */ |
| | | public String getDn() |
| | | { |
| | | return dn; |
| | | } |
| | | |
| | | /** |
| | | * Set the DN. |
| | | * @param dn The dn that must now be used for this message. |
| | | */ |
| | | public void setDn(String dn) |
| | | { |
| | | this.dn = dn; |
| | | } |
| | | |
| | | /** |
| | | * Get the Unique Identifier of the entry on which the operation happened. |
| | | * |
| | | * @return The Unique Identifier of the entry on which the operation happened. |
| | | */ |
| | | public String getUniqueId() |
| | | { |
| | | return uniqueId; |
| | | } |
| | | |
| | | /** |
| | | * Get a boolean indicating if the Update must be processed as an |
| | | * Asynchronous or as an assured replication. |
| | | * |
| | |
| | | return changeNumber.compareTo(msg.getChangeNumber()); |
| | | } |
| | | |
| | | /** |
| | | * Create and Operation from the message. |
| | | * |
| | | * @param conn connection to use when creating the message |
| | | * @return the created Operation |
| | | * @throws LDAPException In case of LDAP decoding exception. |
| | | * @throws ASN1Exception In case of ASN1 decoding exception. |
| | | * @throws DataFormatException In case of bad msg format. |
| | | */ |
| | | public AbstractOperation createOperation(InternalClientConnection conn) |
| | | throws LDAPException, ASN1Exception, DataFormatException |
| | | { |
| | | return createOperation(conn, dn); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Create and Operation from the message using the provided DN. |
| | | * |
| | | * @param conn connection to use when creating the message. |
| | | * @param newDn the DN to use when creating the operation. |
| | | * @return the created Operation. |
| | | * @throws LDAPException In case of LDAP decoding exception. |
| | | * @throws ASN1Exception In case of ASN1 decoding exception. |
| | | * @throws DataFormatException In case of bad msg format. |
| | | */ |
| | | public abstract AbstractOperation createOperation( |
| | | InternalClientConnection conn, String newDn) |
| | | throws LDAPException, ASN1Exception, DataFormatException; |
| | | |
| | | /** |
| | | * Encode the common header for all the UpdateMsg. This uses the current |
| | | * protocol version. |
| | | * |
| | | * @param type the type of UpdateMsg to encode. |
| | | * @param additionalLength additional length needed to encode the remaining |
| | | * part of the UpdateMsg. |
| | | * @return a byte array containing the common header and enough space to |
| | | * encode the remaining bytes of the UpdateMsg as was specified |
| | | * by the additionalLength. |
| | | * (byte array length = common header length + additionalLength) |
| | | * @throws UnsupportedEncodingException if UTF-8 is not supported. |
| | | */ |
| | | public byte[] encodeHeader(byte type, int additionalLength) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | byte[] byteDn = dn.getBytes("UTF-8"); |
| | | byte[] changeNumberByte = |
| | | this.getChangeNumber().toString().getBytes("UTF-8"); |
| | | byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8"); |
| | | |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><dn><entryuuid><assured> |
| | | * <assured mode> <safe data level> |
| | | * the length of result byte array is therefore : |
| | | * 1 + 1 + change number length + 1 + dn length + 1 + uuid length + 1 + 1 |
| | | * + 1 + 1 + additional_length |
| | | */ |
| | | int length = 8 + changeNumberByte.length + byteDn.length |
| | | + byteEntryuuid.length + additionalLength; |
| | | |
| | | byte[] encodedMsg = new byte[length]; |
| | | |
| | | /* put the type of the operation */ |
| | | encodedMsg[0] = type; |
| | | |
| | | /* put the protocol version */ |
| | | encodedMsg[1] = (byte)ProtocolVersion.getCurrentVersion(); |
| | | int pos = 2; |
| | | |
| | | /* Put the ChangeNumber */ |
| | | pos = addByteArray(changeNumberByte, encodedMsg, pos); |
| | | |
| | | /* Put the DN and a terminating 0 */ |
| | | pos = addByteArray(byteDn, encodedMsg, pos); |
| | | |
| | | /* Put the entry uuid and a terminating 0 */ |
| | | pos = addByteArray(byteEntryuuid, encodedMsg, pos); |
| | | |
| | | /* Put the assured flag */ |
| | | encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0); |
| | | |
| | | /* Put the assured mode */ |
| | | encodedMsg[pos++] = assuredMode.getValue(); |
| | | |
| | | /* Put the safe data level */ |
| | | encodedMsg[pos++] = safeDataLevel; |
| | | |
| | | return encodedMsg; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | |
| | | { |
| | | return getBytes(); |
| | | } |
| | | |
| | | // Supported older protocol versions |
| | | switch (reqProtocolVersion) |
| | | else |
| | | { |
| | | case ProtocolVersion.REPLICATION_PROTOCOL_V1: |
| | | return getBytes_V1(); |
| | | default: |
| | | // Unsupported requested version |
| | | throw new UnsupportedEncodingException(getClass().getSimpleName() + |
| | | throw new UnsupportedEncodingException(getClass().getSimpleName() + |
| | | " PDU does not support requested protocol version serialization: " + |
| | | reqProtocolVersion); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the byte array representation of this Message. This uses the version |
| | | * 1 of the replication protocol (used for compatibility purpose). |
| | | * |
| | | * @return The byte array representation of this Message. |
| | | * |
| | | * @throws UnsupportedEncodingException When the encoding of the message |
| | | * failed because the UTF-8 encoding is not supported. |
| | | */ |
| | | public abstract byte[] getBytes_V1() throws UnsupportedEncodingException; |
| | | |
| | | /** |
| | | * Encode the common header for all the UpdateMessage. This uses the version |
| | | * 1 of the replication protocol (used for compatibility purpose). |
| | | * |
| | | * @param type the type of UpdateMessage to encode. |
| | | * @param additionalLength additional length needed to encode the remaining |
| | | * part of the UpdateMessage. |
| | | * @return a byte array containing the common header and enough space to |
| | | * encode the remaining bytes of the UpdateMessage as was specified |
| | | * by the additionalLength. |
| | | * (byte array length = common header length + additionalLength) |
| | | * @throws UnsupportedEncodingException if UTF-8 is not supported. |
| | | */ |
| | | public byte[] encodeHeader_V1(byte type, int additionalLength) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | byte[] byteDn = dn.getBytes("UTF-8"); |
| | | byte[] changeNumberByte = |
| | | this.getChangeNumber().toString().getBytes("UTF-8"); |
| | | byte[] byteEntryuuid = getUniqueId().getBytes("UTF-8"); |
| | | |
| | | /* The message header is stored in the form : |
| | | * <operation type>changenumber><dn><assured><entryuuid><change> |
| | | * the length of result byte array is therefore : |
| | | * 1 + change number length + 1 + dn length + 1 + 1 + |
| | | * uuid length + 1 + additional_length |
| | | */ |
| | | int length = 5 + changeNumberByte.length + byteDn.length |
| | | + byteEntryuuid.length + additionalLength; |
| | | |
| | | byte[] encodedMsg = new byte[length]; |
| | | |
| | | /* put the type of the operation */ |
| | | encodedMsg[0] = type; |
| | | int pos = 1; |
| | | |
| | | /* put the ChangeNumber */ |
| | | pos = addByteArray(changeNumberByte, encodedMsg, pos); |
| | | |
| | | /* put the assured information */ |
| | | encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0); |
| | | |
| | | /* put the DN and a terminating 0 */ |
| | | pos = addByteArray(byteDn, encodedMsg, pos); |
| | | |
| | | /* put the entry uuid and a terminating 0 */ |
| | | pos = addByteArray(byteEntryuuid, encodedMsg, pos); |
| | | |
| | | return encodedMsg; |
| | | } |
| | | |
| | | /** |
| | | * Decode the Header part of this Update Message, and check its type. |
| | | * |
| | | * @param types The allowed types of this Update Message. |
| | | * @param encodedMsg the encoded form of the UpdateMsg. |
| | | * @return the position at which the remaining part of the message starts. |
| | | * @throws DataFormatException if the encodedMsg does not contain a valid |
| | | * common header. |
| | | */ |
| | | public int decodeHeader(byte[] types, byte[] encodedMsg) |
| | | throws DataFormatException |
| | | { |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><dn><entryuuid><assured> |
| | | * <assured mode> <safe data level> |
| | | */ |
| | | |
| | | /* first byte is the type */ |
| | | boolean foundMatchingType = false; |
| | | for (int i = 0; i < types.length; i++) |
| | | { |
| | | if (types[i] == encodedMsg[0]) |
| | | { |
| | | foundMatchingType = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!foundMatchingType) |
| | | throw new DataFormatException("byte[] is not a valid update msg: " |
| | | + encodedMsg[0]); |
| | | |
| | | /* |
| | | * For older protocol version PDUs, decode the matching version header |
| | | * instead. |
| | | */ |
| | | if ((encodedMsg[0] == MSG_TYPE_ADD_V1) || |
| | | (encodedMsg[0] == MSG_TYPE_DELETE_V1) || |
| | | (encodedMsg[0] == MSG_TYPE_MODIFYDN_V1) || |
| | | (encodedMsg[0] == MSG_TYPE_MODIFY_V1)) |
| | | { |
| | | return decodeHeader_V1(encodedMsg); |
| | | } |
| | | |
| | | /* read the protocol version */ |
| | | protocolVersion = (short)encodedMsg[1]; |
| | | |
| | | try |
| | | { |
| | | /* Read the changeNumber */ |
| | | int pos = 2; |
| | | int length = getNextLength(encodedMsg, pos); |
| | | String changenumberStr = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | changeNumber = new ChangeNumber(changenumberStr); |
| | | |
| | | /* Read the dn */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | dn = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | /* Read the entryuuid */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | uniqueId = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | /* Read the assured information */ |
| | | if (encodedMsg[pos++] == 1) |
| | | assuredFlag = true; |
| | | else |
| | | assuredFlag = false; |
| | | |
| | | /* Read the assured mode */ |
| | | assuredMode = AssuredMode.valueOf(encodedMsg[pos++]); |
| | | |
| | | /* Read the safe data level */ |
| | | safeDataLevel = encodedMsg[pos++]; |
| | | |
| | | return pos; |
| | | } catch (UnsupportedEncodingException e) |
| | | { |
| | | throw new DataFormatException("UTF-8 is not supported by this jvm."); |
| | | } catch (IllegalArgumentException e) |
| | | { |
| | | throw new DataFormatException(e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Decode the Header part of this Update Message, and check its type. This |
| | | * uses the version 1 of the replication protocol (used for compatibility |
| | | * purpose). |
| | | * |
| | | * @param encodedMsg the encoded form of the UpdateMessage. |
| | | * @return the position at which the remaining part of the message starts. |
| | | * @throws DataFormatException if the encodedMsg does not contain a valid |
| | | * common header. |
| | | */ |
| | | public int decodeHeader_V1(byte[] encodedMsg) |
| | | throws DataFormatException |
| | | { |
| | | if ((encodedMsg[0] != MSG_TYPE_ADD_V1) && |
| | | (encodedMsg[0] != MSG_TYPE_DELETE_V1) && |
| | | (encodedMsg[0] != MSG_TYPE_MODIFYDN_V1) && |
| | | (encodedMsg[0] != MSG_TYPE_MODIFY_V1)) |
| | | throw new DataFormatException("byte[] is not a valid update msg: expected" |
| | | + " a V1 PDU, received: " + encodedMsg[0]); |
| | | |
| | | // Force version to V1 (other new parameters take their default values |
| | | // (assured stuff...)) |
| | | protocolVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1; |
| | | |
| | | try |
| | | { |
| | | /* read the changeNumber */ |
| | | int pos = 1; |
| | | int length = getNextLength(encodedMsg, pos); |
| | | String changenumberStr = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | changeNumber = new ChangeNumber(changenumberStr); |
| | | |
| | | /* read the assured information */ |
| | | if (encodedMsg[pos++] == 1) |
| | | assuredFlag = true; |
| | | else |
| | | assuredFlag = false; |
| | | |
| | | /* read the dn */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | dn = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | /* read the entryuuid */ |
| | | length = getNextLength(encodedMsg, pos); |
| | | uniqueId = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | |
| | | return pos; |
| | | } catch (UnsupportedEncodingException e) |
| | | { |
| | | throw new DataFormatException("UTF-8 is not supported by this jvm."); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the assured mode in this message. |
| | | * @return The assured mode in this message |
| | | */ |
| | |
| | | * |
| | | * @return The number of bytes used by this message. |
| | | */ |
| | | public abstract int size(); |
| | | public int size() |
| | | { |
| | | return 10 + payload.length; |
| | | } |
| | | |
| | | /** |
| | | * Encode the common header for all the UpdateMsg. This uses the current |
| | | * protocol version. |
| | | * |
| | | * @param type the type of UpdateMsg to encode. |
| | | * @param additionalLength additional length needed to encode the remaining |
| | | * part of the UpdateMsg. |
| | | * @return a byte array containing the common header and enough space to |
| | | * encode the remaining bytes of the UpdateMsg as was specified |
| | | * by the additionalLength. |
| | | * (byte array length = common header length + additionalLength) |
| | | * @throws UnsupportedEncodingException if UTF-8 is not supported. |
| | | */ |
| | | protected byte[] encodeHeader(byte type, int additionalLength) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | byte[] changeNumberByte = |
| | | this.getChangeNumber().toString().getBytes("UTF-8"); |
| | | |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><assured> |
| | | * <assured mode> <safe data level> |
| | | * the length of result byte array is therefore : |
| | | * 1 + 1 + change number length + 1 + 1 |
| | | * + 1 + 1 + additional_length |
| | | */ |
| | | int length = 6 + changeNumberByte.length + additionalLength; |
| | | |
| | | byte[] encodedMsg = new byte[length]; |
| | | |
| | | /* put the type of the operation */ |
| | | encodedMsg[0] = type; |
| | | |
| | | /* put the protocol version */ |
| | | encodedMsg[1] = (byte)ProtocolVersion.getCurrentVersion(); |
| | | int pos = 2; |
| | | |
| | | /* Put the ChangeNumber */ |
| | | pos = addByteArray(changeNumberByte, encodedMsg, pos); |
| | | |
| | | /* Put the assured flag */ |
| | | encodedMsg[pos++] = (assuredFlag ? (byte) 1 : 0); |
| | | |
| | | /* Put the assured mode */ |
| | | encodedMsg[pos++] = assuredMode.getValue(); |
| | | |
| | | /* Put the safe data level */ |
| | | encodedMsg[pos++] = safeDataLevel; |
| | | |
| | | return encodedMsg; |
| | | } |
| | | |
| | | /** |
| | | * Decode the Header part of this Update Message, and check its type. |
| | | * |
| | | * @param type The allowed type of this Update Message. |
| | | * @param encodedMsg the encoded form of the UpdateMsg. |
| | | * @return the position at which the remaining part of the message starts. |
| | | * @throws DataFormatException if the encodedMsg does not contain a valid |
| | | * common header. |
| | | */ |
| | | protected int decodeHeader(byte type, byte[] encodedMsg) |
| | | throws DataFormatException |
| | | { |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><assured> |
| | | * <assured mode> <safe data level> |
| | | */ |
| | | if (!(type == encodedMsg[0])) |
| | | throw new DataFormatException("byte[] is not a valid update msg: " |
| | | + encodedMsg[0]); |
| | | |
| | | /* read the protocol version */ |
| | | protocolVersion = (short)encodedMsg[1]; |
| | | |
| | | try |
| | | { |
| | | /* Read the changeNumber */ |
| | | int pos = 2; |
| | | int length = getNextLength(encodedMsg, pos); |
| | | String changenumberStr = new String(encodedMsg, pos, length, "UTF-8"); |
| | | pos += length + 1; |
| | | changeNumber = new ChangeNumber(changenumberStr); |
| | | |
| | | /* Read the assured information */ |
| | | if (encodedMsg[pos++] == 1) |
| | | assuredFlag = true; |
| | | else |
| | | assuredFlag = false; |
| | | |
| | | /* Read the assured mode */ |
| | | assuredMode = AssuredMode.valueOf(encodedMsg[pos++]); |
| | | |
| | | /* Read the safe data level */ |
| | | safeDataLevel = encodedMsg[pos++]; |
| | | |
| | | return pos; |
| | | } catch (UnsupportedEncodingException e) |
| | | { |
| | | throw new DataFormatException("UTF-8 is not supported by this jvm."); |
| | | } catch (IllegalArgumentException e) |
| | | { |
| | | throw new DataFormatException(e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public byte[] getBytes() throws UnsupportedEncodingException |
| | | { |
| | | /* Encode the header in a byte[] large enough to also contain the payload */ |
| | | byte [] resultByteArray = |
| | | encodeHeader(MSG_TYPE_GENERIC_UPDATE, payload.length); |
| | | |
| | | int pos = resultByteArray.length - payload.length; |
| | | |
| | | /* Add the payload */ |
| | | for (int i=0; i<payload.length; i++,pos++) |
| | | { |
| | | resultByteArray[pos] = payload[i]; |
| | | } |
| | | return resultByteArray; |
| | | } |
| | | |
| | | /** |
| | | * Get the payload of the UpdateMsg. |
| | | * |
| | | * @return The payload of the UpdateMsg. |
| | | */ |
| | | public byte[] getPayload() |
| | | { |
| | | return payload; |
| | | } |
| | | } |
| | |
| | | @Override |
| | | public String toString() |
| | | { |
| | | return "ServerStartMsg content: " + |
| | | "\nnumAck: " + numAck; |
| | | return "WindowMsg : " + "numAck: " + numAck; |
| | | } |
| | | } |
| | |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.Attributes; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.util.TimeThread; |
| | | import org.opends.server.core.DirectoryServer; |
| | |
| | | private ChangeNumber firstChange = null; |
| | | private ChangeNumber lastChange = null; |
| | | private short serverId; |
| | | private DN baseDn; |
| | | private String baseDn; |
| | | private DbMonitorProvider dbMonitor = new DbMonitorProvider(); |
| | | private boolean shutdown = false; |
| | | private boolean done = false; |
| | |
| | | * @throws DatabaseException If a database problem happened |
| | | */ |
| | | public DbHandler( |
| | | short id, DN baseDn, ReplicationServer replicationServer, |
| | | short id, String baseDn, ReplicationServer replicationServer, |
| | | ReplicationDbEnv dbenv, int queueSize) |
| | | throws DatabaseException |
| | | { |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | |
| | | package org.opends.server.replication.server; |
| | | |
| | | import org.opends.server.replication.common.AssuredMode; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.protocol.AckMsg; |
| | | |
| | | /** |
| | | * This class is the mother class for sub-classes holding any information needed |
| | | * about the acks that the replication server will wait for, when he receives an |
| | | * update message with the assured flag on (assured replication acknowledgments |
| | | * expected). |
| | | * It also includes info/routines for constructing the final ack to be sent to |
| | | * the sender of the update message. |
| | | * |
| | | * It is expected to have one sub-class per assured replication sub mode. |
| | | */ |
| | | public abstract class ExpectedAcksInfo |
| | | { |
| | | // The server handler of the server that sent the assured update message and |
| | | // to whow we want to return the final ack |
| | | private ServerHandler requesterServerHandler = null; |
| | | |
| | | // The requested assured mode of matcching update message |
| | | private AssuredMode assuredMode = null; |
| | | |
| | | /** |
| | | * The change number of the assured update message we want acks for. |
| | | */ |
| | | protected ChangeNumber changeNumber = null; |
| | | |
| | | /** |
| | | * Is the treatment of the acks for the update message completed or not ? |
| | | * This is used for concurrent access to this object by either the assured |
| | | * timeout task or the code for processing an ack for the matching update |
| | | * message. This should be set to true when the treatment of the expected |
| | | * acks is completed or an ack timeout has occured and we are going to remove |
| | | * this object from the map where it is stored. |
| | | */ |
| | | private boolean completed = false; |
| | | |
| | | /** |
| | | * Creates a new ExpectedAcksInfo. |
| | | * @param changeNumber The change number of the assured update message |
| | | * @param requesterServerHandler The server handler of the server that sent |
| | | * the assured update message |
| | | * @param assuredMode The assured mode requested by the assured update message |
| | | */ |
| | | protected ExpectedAcksInfo(ChangeNumber changeNumber, |
| | | ServerHandler requesterServerHandler, AssuredMode assuredMode) |
| | | { |
| | | this.requesterServerHandler = requesterServerHandler; |
| | | this.assuredMode = assuredMode; |
| | | this.changeNumber = changeNumber; |
| | | } |
| | | |
| | | /** |
| | | * Gets the server handler of the server which requested the acknowledgments. |
| | | * @return The server handler of the server which requested the |
| | | * acknowledgments. |
| | | */ |
| | | public ServerHandler getRequesterServer() |
| | | { |
| | | return requesterServerHandler; |
| | | } |
| | | |
| | | /** |
| | | * Gets the requested assured mode for the matching update message. |
| | | * @return The requested assured mode for the matching update message. |
| | | */ |
| | | public AssuredMode getAssuredMode() |
| | | { |
| | | return assuredMode; |
| | | } |
| | | |
| | | /** |
| | | * Process the received ack from a server we are waiting an ack from. |
| | | * @param ackingServer The server handler of the server that sent the ack |
| | | * @param ackMsg The ack message to process |
| | | * @return True if the expected number of acks has just been reached |
| | | */ |
| | | public abstract boolean processReceivedAck(ServerHandler ackingServer, |
| | | AckMsg ackMsg); |
| | | |
| | | /** |
| | | * Creates the ack message to be returned to the requester server, taking into |
| | | * account the information in the received acks from every servers. |
| | | * @param timeout True if we call this method when the timeout occurred, that |
| | | * is we did not received every expected acks in time, and thus, the timeout |
| | | * flag should also be enabled in the returned ack message. |
| | | * @return The ack message ready to be sent to the requester server |
| | | */ |
| | | public abstract AckMsg createAck(boolean timeout); |
| | | |
| | | /** |
| | | * Has the treatment of this object been completed or not? |
| | | * If true is returned, one must not modify this object (useless) nor remove |
| | | * it from the map where it is stored (will be or has already been done by the |
| | | * other code (ack timeout code, or ack processing code)). |
| | | * @return True if treatment of this object has been completed. |
| | | */ |
| | | public boolean isCompleted() |
| | | { |
| | | return completed; |
| | | } |
| | | |
| | | /** |
| | | * Signal that treatment of this object has been completed and that it is |
| | | * going to be removed from the map where it is stored. |
| | | */ |
| | | public void completed() |
| | | { |
| | | completed = true; |
| | | } |
| | | } |
| | |
| | | attributes.add(Attributes.create("server-id", |
| | | String.valueOf(serverId))); |
| | | attributes.add(Attributes.create("base-dn", |
| | | rsDomain.getBaseDn().toNormalizedString())); |
| | | rsDomain.getBaseDn())); |
| | | attributes.add(Attributes.create("connected-to", |
| | | replServerHandler.getMonitorInstanceName())); |
| | | |
| | |
| | | private ConcurrentHashMap<Short, ServerState> LDAPStates = |
| | | new ConcurrentHashMap<Short, ServerState>(); |
| | | |
| | | // A Map containing the ServerStates of each RS. |
| | | private ConcurrentHashMap<Short, ServerState> RSStates = |
| | | new ConcurrentHashMap<Short, ServerState>(); |
| | | |
| | | // For each LDAP server, the last(max) CN it published |
| | | private ConcurrentHashMap<Short, ChangeNumber> maxCNs = |
| | | new ConcurrentHashMap<Short, ChangeNumber>(); |
| | |
| | | private ConcurrentHashMap<Short, Long> fmRSDate = |
| | | new ConcurrentHashMap<Short, Long>(); |
| | | |
| | | private ConcurrentHashMap<Short, Long> missingChangesRS = |
| | | new ConcurrentHashMap<Short, Long>(); |
| | | |
| | | |
| | | /** |
| | | * Get an approximation of the latency delay of the replication. |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the number of missing changes for a Replication Server. |
| | | * |
| | | * @param serverId The server ID. |
| | | * |
| | | * @return The number of missing changes. |
| | | */ |
| | | public long getMissingChangesRS(short serverId) |
| | | { |
| | | Long res = missingChangesRS.get(serverId); |
| | | if (res==null) |
| | | return 0; |
| | | else |
| | | return res; |
| | | } |
| | | |
| | | /** |
| | | * Build the monitor data that are computed from the collected ones. |
| | | */ |
| | | public void completeComputing() |
| | | { |
| | | String mds = ""; |
| | | |
| | | // Computes the missing changes counters |
| | | // Computes the missing changes counters for LDAP servers |
| | | // For each LSi , |
| | | // Regarding each other LSj |
| | | // Sum the difference : max(LSj) - state(LSi) |
| | |
| | | } |
| | | mds += "=" + lsiMissingChanges; |
| | | this.missingChanges.put(lsiSid,lsiMissingChanges); |
| | | } |
| | | |
| | | // Computes the missing changes counters for RS : |
| | | // Sum the difference of seqnuence numbers for each element in the States. |
| | | |
| | | for (short lsiSid : RSStates.keySet()) |
| | | { |
| | | ServerState lsiState = this.RSStates.get(lsiSid); |
| | | Long lsiMissingChanges = (long)0; |
| | | if (lsiState != null) |
| | | { |
| | | Iterator<Short> lsjMaxItr = this.maxCNs.keySet().iterator(); |
| | | while (lsjMaxItr.hasNext()) |
| | | { |
| | | Short lsjSid = lsjMaxItr.next(); |
| | | ChangeNumber lsjMaxCN = this.maxCNs.get(lsjSid); |
| | | ChangeNumber lsiLastCN = lsiState.getMaxChangeNumber(lsjSid); |
| | | |
| | | int missingChangesLsiLsj = |
| | | ChangeNumber.diffSeqNum(lsjMaxCN, lsiLastCN); |
| | | |
| | | mds += |
| | | "+ diff("+lsjMaxCN+"-" |
| | | +lsiLastCN+")="+missingChangesLsiLsj; |
| | | |
| | | lsiMissingChanges += missingChangesLsiLsj; |
| | | } |
| | | } |
| | | mds += "=" + lsiMissingChanges; |
| | | this.missingChangesRS.put(lsiSid,lsiMissingChanges); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | |
| | | } |
| | | |
| | | /** |
| | | * Set the state of the RS with the provided serverId. |
| | | * |
| | | * @param serverId The server ID. |
| | | * @param state The server state. |
| | | */ |
| | | public void setRSState(short serverId, ServerState state) |
| | | { |
| | | RSStates.put(serverId, state); |
| | | } |
| | | |
| | | /** |
| | | * Set the state of the LDAP server with the provided serverId. |
| | | * @param serverId The server ID. |
| | | * @param newFmd The first missing date. |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.server; |
| | | |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.util.zip.DataFormatException; |
| | | import org.opends.server.replication.common.AssuredMode; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | import org.opends.server.replication.protocol.ProtocolVersion; |
| | | |
| | | /** |
| | | * This is a facility class that is in fact an hack to optimize replication |
| | | * server performances in case of assured replication usage: |
| | | * When received from a server by a server reader, an update message is to be |
| | | * posted in the queues of the server writers. Thus, they receive the same |
| | | * reference of update message. As we want to transform an assured update |
| | | * message to an equivalent not assured one for some servers but not for all, |
| | | * instead of performing a painful clone of the message, we use this special |
| | | * class to keep a reference to the real object, but that will overwrite the |
| | | * assured flag value to false when serializing the message, and return false |
| | | * when calling the isAssured() method. |
| | | * |
| | | */ |
| | | public class NotAssuredUpdateMsg extends UpdateMsg |
| | | { |
| | | // The real update message this message represents |
| | | private UpdateMsg realUpdateMsg = null; |
| | | |
| | | /** |
| | | * Creates a new empty UpdateMsg. |
| | | * This class is only used by replication server code so constructor is not |
| | | * public by security. |
| | | * @param updateMsg The real underlying update message this object represents. |
| | | */ |
| | | NotAssuredUpdateMsg(UpdateMsg updateMsg) |
| | | { |
| | | realUpdateMsg = updateMsg; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public ChangeNumber getChangeNumber() |
| | | { |
| | | return realUpdateMsg.getChangeNumber(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isAssured() |
| | | { |
| | | // Always return false as we represent a not assured message |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void setAssured(boolean assured) |
| | | { |
| | | // No impact for this method as semantic is that assured is always false |
| | | // and we do not want to change the original real update message settings |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean equals(Object obj) |
| | | { |
| | | // Compare with the underlying real update message |
| | | if (obj != null) |
| | | { |
| | | if (obj.getClass() != realUpdateMsg.getClass()) |
| | | return false; |
| | | return realUpdateMsg.getChangeNumber(). |
| | | equals(((UpdateMsg)obj).getChangeNumber()); |
| | | } |
| | | else |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | return realUpdateMsg.hashCode(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int compareTo(UpdateMsg msg) |
| | | { |
| | | return realUpdateMsg.compareTo(msg); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public byte[] getBytes(short reqProtocolVersion) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | // Get the encoding of the real message then overwrite the assured flag byte |
| | | // to always be false |
| | | byte[] bytes = realUpdateMsg.getBytes(reqProtocolVersion); |
| | | int maxLen = bytes.length; |
| | | int pos = -1; |
| | | int nZeroFound = 0; // Number of 0 value found |
| | | boolean found = false; |
| | | |
| | | /** |
| | | * Overwrite the assured flag at the right position according to |
| | | * message type. |
| | | */ |
| | | // Look for assured flag position |
| | | if (realUpdateMsg instanceof LDAPUpdateMsg) |
| | | { |
| | | // LDAP update message |
| | | switch (reqProtocolVersion) |
| | | { |
| | | case ProtocolVersion.REPLICATION_PROTOCOL_V1: |
| | | /* The message header is stored in the form : |
| | | * <operation type><changenumber><dn><assured><entryuuid><change> |
| | | * the length of result byte array is therefore : |
| | | * 1 + change number length + 1 + dn length + 1 + 1 + |
| | | * uuid length + 1 + additional_length |
| | | * See LDAPUpdateMsg.encodeHeader_V1() for more information |
| | | */ |
| | | // Find end of change number then end of dn |
| | | for (pos = 1 ; pos < maxLen ; pos++ ) { |
| | | if (bytes[pos] == (byte)0) |
| | | { |
| | | nZeroFound++; |
| | | if (nZeroFound == 2) // 2 end of string to find |
| | | { |
| | | found = true; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (!found) |
| | | throw new UnsupportedEncodingException("Could not find end of " + |
| | | "change number."); |
| | | pos++; |
| | | if (pos >= maxLen) |
| | | throw new UnsupportedEncodingException("Reached end of packet."); |
| | | // Force assured flag to false |
| | | bytes[pos] = (byte)0; |
| | | break; |
| | | case ProtocolVersion.REPLICATION_PROTOCOL_V2: |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><dn><entryuuid> |
| | | * <assured> <assured mode> <safe data level> |
| | | * the length of result byte array is therefore : |
| | | * 1 + 1 + change number length + 1 + dn length + 1 + uuid length + |
| | | * 1 + 1 + 1 + 1 + additional_length |
| | | * See LDAPUpdateMsg.encodeHeader() for more information |
| | | */ |
| | | // Find end of change number then end of dn then end of uuid |
| | | for (pos = 2 ; pos < maxLen ; pos++ ) { |
| | | if (bytes[pos] == (byte)0) |
| | | { |
| | | nZeroFound++; |
| | | if (nZeroFound == 3) // 3 end of string to find |
| | | { |
| | | found = true; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (!found) |
| | | throw new UnsupportedEncodingException("Could not find end of " + |
| | | "change number."); |
| | | pos++; |
| | | if (pos >= maxLen) |
| | | throw new UnsupportedEncodingException("Reached end of packet."); |
| | | // Force assured flag to false |
| | | bytes[pos] = (byte)0; |
| | | break; |
| | | default: |
| | | throw new UnsupportedEncodingException("Unsupported requested " + |
| | | " protocol version: " + reqProtocolVersion); |
| | | } |
| | | } else |
| | | { |
| | | if (!(realUpdateMsg instanceof UpdateMsg)) |
| | | { |
| | | // Should never happen |
| | | throw new UnsupportedEncodingException( |
| | | "Unknown underlying real message type."); |
| | | } |
| | | // This is a generic update message |
| | | /* The message header is stored in the form : |
| | | * <operation type><protocol version><changenumber><assured> |
| | | * <assured mode> <safe data level> |
| | | * the length of result byte array is therefore : |
| | | * 1 + 1 + change number length + 1 + 1 |
| | | * + 1 + 1 + additional_length |
| | | * See UpdateMsg.encodeHeader() for more information |
| | | */ |
| | | // Find end of change number |
| | | for (pos = 2 ; pos < maxLen ; pos++ ) |
| | | { |
| | | if (bytes[pos] == (byte)0) |
| | | { |
| | | nZeroFound++; |
| | | if (nZeroFound == 1) // 1 end of string to find |
| | | { |
| | | found = true; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (!found) |
| | | throw new UnsupportedEncodingException("Could not find end of " + |
| | | "change number."); |
| | | pos++; |
| | | if (pos >= maxLen) |
| | | throw new UnsupportedEncodingException("Reached end of packet."); |
| | | // Force assured flag to false |
| | | bytes[pos] = (byte) 0; |
| | | } |
| | | return bytes; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public byte[] getBytes() throws UnsupportedEncodingException |
| | | { |
| | | return getBytes(ProtocolVersion.getCurrentVersion()); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public AssuredMode getAssuredMode() |
| | | { |
| | | return realUpdateMsg.getAssuredMode(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public byte getSafeDataLevel() |
| | | { |
| | | return realUpdateMsg.getSafeDataLevel(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void setAssuredMode(AssuredMode assuredMode) |
| | | { |
| | | // No impact for this method as semantic is that assured is always false |
| | | // and we do not want to change the original real update message settings |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void setSafeDataLevel(byte safeDataLevel) |
| | | { |
| | | // No impact for this method as semantic is that assured is always false |
| | | // and we do not want to change the original real update message settings |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public short getVersion() |
| | | { |
| | | return realUpdateMsg.getVersion(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int size() |
| | | { |
| | | return realUpdateMsg.size(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | protected byte[] encodeHeader(byte type, int additionalLength) |
| | | throws UnsupportedEncodingException |
| | | { |
| | | // No called as only used by constructors using bytes |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int decodeHeader(byte type, byte[] encodedMsg) |
| | | throws DataFormatException |
| | | { |
| | | // No called as only used by getBytes methods |
| | | return -1; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public byte[] getPayload() |
| | | { |
| | | return realUpdateMsg.getPayload(); |
| | | } |
| | | } |
| | |
| | | import static org.opends.server.loggers.debug.DebugLogger.getTracer; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.ByteArrayOutputStream; |
| | |
| | | /** |
| | | * Export one change. |
| | | */ |
| | | private void processChange(UpdateMsg msg, |
| | | private void processChange(UpdateMsg updateMsg, |
| | | LDIFExportConfig exportConfig, LDIFWriter ldifWriter, |
| | | SearchOperation searchOperation, String baseDN) |
| | | { |
| | |
| | | |
| | | try |
| | | { |
| | | if (msg instanceof AddMsg) |
| | | if (updateMsg instanceof LDAPUpdateMsg) |
| | | { |
| | | AddMsg addMsg = (AddMsg)msg; |
| | | AddOperation addOperation = (AddOperation)msg.createOperation(conn); |
| | | LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg; |
| | | |
| | | dn = DN.decode("puid=" + addMsg.getParentUid() + "+" + |
| | | CHANGE_NUMBER + "=" + msg.getChangeNumber().toString() + "+" + |
| | | msg.getDn() + "," + BASE_DN); |
| | | |
| | | Map<AttributeType,List<Attribute>> attributes = |
| | | new HashMap<AttributeType,List<Attribute>>(); |
| | | Map<ObjectClass, String> objectclasses = |
| | | new HashMap<ObjectClass, String>(); |
| | | |
| | | for (RawAttribute a : addOperation.getRawAttributes()) |
| | | if (msg instanceof AddMsg) |
| | | { |
| | | Attribute attr = a.toAttribute(); |
| | | if (attr.getAttributeType().isObjectClassType()) |
| | | { |
| | | for (ByteString os : a.getValues()) |
| | | { |
| | | String ocName = os.toString(); |
| | | ObjectClass oc = |
| | | DirectoryServer.getObjectClass(toLowerCase(ocName)); |
| | | if (oc == null) |
| | | { |
| | | oc = DirectoryServer.getDefaultObjectClass(ocName); |
| | | } |
| | | AddMsg addMsg = (AddMsg)msg; |
| | | AddOperation addOperation = (AddOperation)msg.createOperation(conn); |
| | | |
| | | objectclasses.put(oc,ocName); |
| | | dn = DN.decode("puid=" + addMsg.getParentUid() + "+" + |
| | | CHANGE_NUMBER + "=" + msg.getChangeNumber().toString() + "+" + |
| | | msg.getDn() + "," + BASE_DN); |
| | | |
| | | Map<AttributeType,List<Attribute>> attributes = |
| | | new HashMap<AttributeType,List<Attribute>>(); |
| | | Map<ObjectClass, String> objectclasses = |
| | | new HashMap<ObjectClass, String>(); |
| | | |
| | | for (RawAttribute a : addOperation.getRawAttributes()) |
| | | { |
| | | Attribute attr = a.toAttribute(); |
| | | if (attr.getAttributeType().isObjectClassType()) |
| | | { |
| | | for (ByteString os : a.getValues()) |
| | | { |
| | | String ocName = os.toString(); |
| | | ObjectClass oc = |
| | | DirectoryServer.getObjectClass(toLowerCase(ocName)); |
| | | if (oc == null) |
| | | { |
| | | oc = DirectoryServer.getDefaultObjectClass(ocName); |
| | | } |
| | | |
| | | objectclasses.put(oc,ocName); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | addAttribute(attributes, attr); |
| | | } |
| | | } |
| | | |
| | | Attribute changetype = Attributes.create("changetype", "add"); |
| | | addAttribute(attributes, changetype); |
| | | |
| | | if (exportConfig != null) |
| | | { |
| | | AddChangeRecordEntry changeRecord = |
| | | new AddChangeRecordEntry(dn, attributes); |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | addAttribute(attributes, attr); |
| | | entry = new Entry(dn, objectclasses, attributes, null); |
| | | } |
| | | } |
| | | else if (msg instanceof DeleteMsg) |
| | | { |
| | | DeleteMsg delMsg = (DeleteMsg)msg; |
| | | |
| | | dn = DN.decode("uuid=" + msg.getUniqueId() + "," + |
| | | CHANGE_NUMBER + "=" + delMsg.getChangeNumber().toString()+ "," + |
| | | msg.getDn() +","+ BASE_DN); |
| | | |
| | | DeleteChangeRecordEntry changeRecord = |
| | | new DeleteChangeRecordEntry(dn); |
| | | if (exportConfig != null) |
| | | { |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | Writer writer = new Writer(); |
| | | LDIFWriter ldifWriter2 = writer.getLDIFWriter(); |
| | | ldifWriter2.writeChangeRecord(changeRecord); |
| | | LDIFReader reader = writer.getLDIFReader(); |
| | | entry = reader.readEntry(); |
| | | } |
| | | } |
| | | else if (msg instanceof ModifyMsg) |
| | | { |
| | | ModifyOperation op = (ModifyOperation)msg.createOperation(conn); |
| | | |
| | | dn = DN.decode("uuid=" + msg.getUniqueId() + "," + |
| | | CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," + |
| | | msg.getDn() +","+ BASE_DN); |
| | | op.setInternalOperation(true); |
| | | |
| | | ModifyChangeRecordEntry changeRecord = |
| | | new ModifyChangeRecordEntry(dn, op.getRawModifications()); |
| | | if (exportConfig != null) |
| | | { |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | Writer writer = new Writer(); |
| | | LDIFWriter ldifWriter2 = writer.getLDIFWriter(); |
| | | ldifWriter2.writeChangeRecord(changeRecord); |
| | | LDIFReader reader = writer.getLDIFReader(); |
| | | entry = reader.readEntry(); |
| | | } |
| | | } |
| | | else if (msg instanceof ModifyDNMsg) |
| | | { |
| | | ModifyDNOperation op = (ModifyDNOperation)msg.createOperation(conn); |
| | | |
| | | dn = DN.decode("uuid=" + msg.getUniqueId() + "," + |
| | | CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," + |
| | | msg.getDn() +","+ BASE_DN); |
| | | op.setInternalOperation(true); |
| | | |
| | | ModifyDNChangeRecordEntry changeRecord = |
| | | new ModifyDNChangeRecordEntry(dn, op.getNewRDN(), op.deleteOldRDN(), |
| | | op.getNewSuperior()); |
| | | |
| | | if (exportConfig != null) |
| | | { |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | Writer writer = new Writer(); |
| | | LDIFWriter ldifWriter2 = writer.getLDIFWriter(); |
| | | ldifWriter2.writeChangeRecord(changeRecord); |
| | | LDIFReader reader = writer.getLDIFReader(); |
| | | Entry modDNEntry = reader.readEntry(); |
| | | entry = modDNEntry; |
| | | } |
| | | } |
| | | |
| | | Attribute changetype = Attributes.create("changetype", "add"); |
| | | addAttribute(attributes, changetype); |
| | | |
| | | if (exportConfig != null) |
| | | { |
| | | AddChangeRecordEntry changeRecord = |
| | | new AddChangeRecordEntry(dn, attributes); |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | this.exportedCount++; |
| | | } |
| | | else |
| | | { |
| | | entry = new Entry(dn, objectclasses, attributes, null); |
| | | } |
| | | } |
| | | else if (msg instanceof DeleteMsg) |
| | | { |
| | | DeleteMsg delMsg = (DeleteMsg)msg; |
| | | // Add extensibleObject objectclass and the ChangeNumber |
| | | // in the entry. |
| | | if (!entry.getObjectClasses().containsKey(objectclass)) |
| | | entry.addObjectClass(objectclass); |
| | | Attribute changeNumber = |
| | | Attributes.create(CHANGE_NUMBER, |
| | | msg.getChangeNumber().toStringUI()); |
| | | addAttribute(entry.getUserAttributes(), changeNumber); |
| | | Attribute domain = Attributes.create("replicationDomain", baseDN); |
| | | addAttribute(entry.getUserAttributes(), domain); |
| | | |
| | | dn = DN.decode("uuid=" + msg.getUniqueId() + "," + |
| | | CHANGE_NUMBER + "=" + delMsg.getChangeNumber().toString()+ "," + |
| | | msg.getDn() +","+ BASE_DN); |
| | | // Get the base DN, scope, and filter for the search. |
| | | DN searchBaseDN = searchOperation.getBaseDN(); |
| | | SearchScope scope = searchOperation.getScope(); |
| | | SearchFilter filter = searchOperation.getFilter(); |
| | | |
| | | DeleteChangeRecordEntry changeRecord = |
| | | new DeleteChangeRecordEntry(dn); |
| | | if (exportConfig != null) |
| | | { |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | Writer writer = new Writer(); |
| | | LDIFWriter ldifWriter2 = writer.getLDIFWriter(); |
| | | ldifWriter2.writeChangeRecord(changeRecord); |
| | | LDIFReader reader = writer.getLDIFReader(); |
| | | entry = reader.readEntry(); |
| | | } |
| | | } |
| | | else if (msg instanceof ModifyMsg) |
| | | { |
| | | ModifyOperation op = (ModifyOperation)msg.createOperation(conn); |
| | | |
| | | dn = DN.decode("uuid=" + msg.getUniqueId() + "," + |
| | | CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," + |
| | | msg.getDn() +","+ BASE_DN); |
| | | op.setInternalOperation(true); |
| | | |
| | | ModifyChangeRecordEntry changeRecord = |
| | | new ModifyChangeRecordEntry(dn, op.getRawModifications()); |
| | | if (exportConfig != null) |
| | | { |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | Writer writer = new Writer(); |
| | | LDIFWriter ldifWriter2 = writer.getLDIFWriter(); |
| | | ldifWriter2.writeChangeRecord(changeRecord); |
| | | LDIFReader reader = writer.getLDIFReader(); |
| | | entry = reader.readEntry(); |
| | | } |
| | | } |
| | | else if (msg instanceof ModifyDNMsg) |
| | | { |
| | | ModifyDNOperation op = (ModifyDNOperation)msg.createOperation(conn); |
| | | |
| | | dn = DN.decode("uuid=" + msg.getUniqueId() + "," + |
| | | CHANGE_NUMBER + "=" + msg.getChangeNumber().toString()+ "," + |
| | | msg.getDn() +","+ BASE_DN); |
| | | op.setInternalOperation(true); |
| | | |
| | | ModifyDNChangeRecordEntry changeRecord = |
| | | new ModifyDNChangeRecordEntry(dn, op.getNewRDN(), op.deleteOldRDN(), |
| | | op.getNewSuperior()); |
| | | |
| | | if (exportConfig != null) |
| | | { |
| | | ldifWriter.writeChangeRecord(changeRecord); |
| | | } |
| | | else |
| | | { |
| | | Writer writer = new Writer(); |
| | | LDIFWriter ldifWriter2 = writer.getLDIFWriter(); |
| | | ldifWriter2.writeChangeRecord(changeRecord); |
| | | LDIFReader reader = writer.getLDIFReader(); |
| | | Entry modDNEntry = reader.readEntry(); |
| | | entry = modDNEntry; |
| | | } |
| | | } |
| | | |
| | | if (exportConfig != null) |
| | | { |
| | | this.exportedCount++; |
| | | } |
| | | else |
| | | { |
| | | // Add extensibleObject objectclass and the ChangeNumber |
| | | // in the entry. |
| | | if (!entry.getObjectClasses().containsKey(objectclass)) |
| | | entry.addObjectClass(objectclass); |
| | | Attribute changeNumber = |
| | | Attributes.create(CHANGE_NUMBER, msg.getChangeNumber().toStringUI()); |
| | | addAttribute(entry.getUserAttributes(), changeNumber); |
| | | Attribute domain = Attributes.create("replicationDomain", baseDN); |
| | | addAttribute(entry.getUserAttributes(), domain); |
| | | |
| | | // Get the base DN, scope, and filter for the search. |
| | | DN searchBaseDN = searchOperation.getBaseDN(); |
| | | SearchScope scope = searchOperation.getScope(); |
| | | SearchFilter filter = searchOperation.getFilter(); |
| | | |
| | | boolean ms = entry.matchesBaseAndScope(searchBaseDN, scope); |
| | | boolean mf = filter.matchesEntry(entry); |
| | | if ( ms && mf ) |
| | | { |
| | | searchOperation.returnEntry(entry, new LinkedList<Control>()); |
| | | boolean ms = entry.matchesBaseAndScope(searchBaseDN, scope); |
| | | boolean mf = filter.matchesEntry(entry); |
| | | if ( ms && mf ) |
| | | { |
| | | searchOperation.returnEntry(entry, new LinkedList<Control>()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | import java.util.List; |
| | | import java.io.UnsupportedEncodingException; |
| | | |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import java.util.concurrent.locks.ReentrantReadWriteLock; |
| | |
| | | private ReplicationDbEnv dbenv = null; |
| | | private ReplicationServer replicationServer; |
| | | private Short serverId; |
| | | private DN baseDn; |
| | | private String baseDn; |
| | | |
| | | // The maximum number of retries in case of DatabaseDeadlock Exception. |
| | | private static final int DEADLOCK_RETRIES = 10; |
| | |
| | | * @param dbenv The Db environment to use to create the db. |
| | | * @throws DatabaseException If a database problem happened. |
| | | */ |
| | | public ReplicationDB(Short serverId, DN baseDn, |
| | | public ReplicationDB(Short serverId, String baseDn, |
| | | ReplicationServer replicationServer, |
| | | ReplicationDbEnv dbenv) |
| | | throws DatabaseException |
| | |
| | | import java.io.File; |
| | | import java.io.UnsupportedEncodingException; |
| | | |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | |
| | | import com.sleepycat.je.Cursor; |
| | | import com.sleepycat.je.Database; |
| | | import com.sleepycat.je.DatabaseConfig; |
| | |
| | | { |
| | | long generationId=-1; |
| | | |
| | | DN baseDn; |
| | | String baseDn; |
| | | |
| | | try |
| | | { |
| | |
| | | + "<" + str[1] + ">")); |
| | | } |
| | | |
| | | // <baseDn> |
| | | baseDn = null; |
| | | try |
| | | { |
| | | baseDn = DN.decode(str[2]); |
| | | } catch (DirectoryException e) |
| | | { |
| | | Message message = |
| | | ERR_IGNORE_BAD_DN_IN_DATABASE_IDENTIFIER.get(str[1]); |
| | | logError(message); |
| | | |
| | | } |
| | | baseDn = str[2]; |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | |
| | | + "<" + str[0] + ">")); |
| | | } |
| | | // <baseDn> |
| | | DN baseDn = null; |
| | | try |
| | | { |
| | | baseDn = DN.decode(str[1]); |
| | | } catch (DirectoryException e) |
| | | { |
| | | Message message = |
| | | ERR_IGNORE_BAD_DN_IN_DATABASE_IDENTIFIER.get(str[1]); |
| | | logError(message); |
| | | } |
| | | String baseDn = str[1]; |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | |
| | | * @return the Database. |
| | | * @throws DatabaseException in case of underlying Exception. |
| | | */ |
| | | public Database getOrAddDb(Short serverId, DN baseDn, Long generationId) |
| | | public Database getOrAddDb(Short serverId, String baseDn, Long generationId) |
| | | throws DatabaseException |
| | | { |
| | | if (debugEnabled()) |
| | |
| | | serverId + " " + baseDn + " " + generationId); |
| | | try |
| | | { |
| | | String stringId = serverId.toString() + FIELD_SEPARATOR |
| | | + baseDn.toNormalizedString(); |
| | | String stringId = serverId.toString() + FIELD_SEPARATOR + baseDn; |
| | | |
| | | // Opens the database for the changes received from this server |
| | | // on this domain. Create it if it does not already exist. |
| | |
| | | |
| | | // Creates the record domain base Dn/ generationId in the stateDb |
| | | // if it does not already exist. |
| | | stringId = GENERATION_ID_TAG + FIELD_SEPARATOR + |
| | | baseDn.toNormalizedString(); |
| | | stringId = GENERATION_ID_TAG + FIELD_SEPARATOR + baseDn; |
| | | String dataStringId = GENERATION_ID_TAG + FIELD_SEPARATOR + |
| | | generationId.toString() + FIELD_SEPARATOR + |
| | | baseDn.toNormalizedString(); |
| | | generationId.toString() + FIELD_SEPARATOR + baseDn; |
| | | byteId = stringId.getBytes("UTF-8"); |
| | | byte[] dataByteId; |
| | | dataByteId = dataStringId.getBytes("UTF-8"); |
| | |
| | | * @param baseDn The baseDn for which the generationID must be cleared. |
| | | * |
| | | */ |
| | | public void clearGenerationId(DN baseDn) |
| | | public void clearGenerationId(String baseDn) |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | |
| | | try |
| | | { |
| | | // Deletes the record domain base Dn/ generationId in the stateDb |
| | | String stringId = GENERATION_ID_TAG + FIELD_SEPARATOR + |
| | | baseDn.toNormalizedString(); |
| | | String stringId = GENERATION_ID_TAG + FIELD_SEPARATOR + baseDn; |
| | | byte[] byteId = stringId.getBytes("UTF-8"); |
| | | DatabaseEntry key = new DatabaseEntry(); |
| | | key.setData(byteId); |
| | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | | "In " + this.replicationServer.getMonitorInstanceName() + |
| | | " clearGenerationId (" + |
| | | baseDn +") succeeded."); |
| | | " clearGenerationId (" + baseDn +") succeeded."); |
| | | } |
| | | catch (DatabaseException dbe) |
| | | { |
| | |
| | | * @param serverId The serverId to remove from the Db. |
| | | * |
| | | */ |
| | | public void clearServerId(DN baseDn, Short serverId) |
| | | public void clearServerId(String baseDn, Short serverId) |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | |
| | | "clearServerId(baseDN=" + baseDn + ", serverId=" + serverId); |
| | | try |
| | | { |
| | | String stringId = serverId.toString() + FIELD_SEPARATOR |
| | | + baseDn.toNormalizedString(); |
| | | String stringId = serverId.toString() + FIELD_SEPARATOR + baseDn; |
| | | |
| | | // Deletes the record serverId/domain base Dn in the stateDb |
| | | byte[] byteId; |
| | |
| | | /* This table is used to store the list of dn for which we are currently |
| | | * handling servers. |
| | | */ |
| | | private ConcurrentHashMap<DN, ReplicationServerDomain> baseDNs = |
| | | new ConcurrentHashMap<DN, ReplicationServerDomain>(); |
| | | private ConcurrentHashMap<String, ReplicationServerDomain> baseDNs = |
| | | new ConcurrentHashMap<String, ReplicationServerDomain>(); |
| | | |
| | | private String localURL = "null"; |
| | | private boolean shutdown = false; |
| | |
| | | assuredTimeout = configuration.getAssuredTimeout(); |
| | | degradedStatusThreshold = configuration.getDegradedStatusThreshold(); |
| | | |
| | | replSessionSecurity = new ReplSessionSecurity(configuration); |
| | | replSessionSecurity = new ReplSessionSecurity(); |
| | | initialize(replicationPort); |
| | | configuration.addChangeListener(this); |
| | | DirectoryServer.registerMonitorProvider(this); |
| | |
| | | * colon. |
| | | * @param baseDn The baseDn of the connection |
| | | */ |
| | | private void connect(String serverURL, DN baseDn) |
| | | private void connect(String serverURL, String baseDn) |
| | | { |
| | | int separator = serverURL.lastIndexOf(':'); |
| | | String port = serverURL.substring(separator + 1); |
| | |
| | | * @return The ReplicationServerDomain associated to the base DN given in |
| | | * parameter. |
| | | */ |
| | | public ReplicationServerDomain getReplicationServerDomain(DN baseDn, |
| | | public ReplicationServerDomain getReplicationServerDomain(String baseDn, |
| | | boolean create) |
| | | { |
| | | ReplicationServerDomain replicationServerDomain; |
| | |
| | | * DN given in parameter. |
| | | * @throws DatabaseException in case of underlying database problem. |
| | | */ |
| | | public DbHandler newDbHandler(short id, DN baseDn) |
| | | public DbHandler newDbHandler(short id, String baseDn) |
| | | throws DatabaseException |
| | | { |
| | | return new DbHandler(id, baseDn, this, dbEnv, queueSize); |
| | |
| | | * @param baseDn The baseDn for which to delete the generationId. |
| | | * @throws DatabaseException When it occurs. |
| | | */ |
| | | public void clearGenerationId(DN baseDn) |
| | | public void clearGenerationId(String baseDn) |
| | | throws DatabaseException |
| | | { |
| | | try |
| | |
| | | * Add all the base DNs that are known by this replication server. |
| | | */ |
| | | AttributeBuilder builder = new AttributeBuilder("base-dn"); |
| | | for (DN base : baseDNs.keySet()) |
| | | for (String base : baseDNs.keySet()) |
| | | { |
| | | builder.add(base.toString()); |
| | | } |
| | |
| | | |
| | | // Publish to monitor the generation ID by replicationServerDomain |
| | | builder = new AttributeBuilder("base-dn-generation-id"); |
| | | for (DN base : baseDNs.keySet()) |
| | | for (String base : baseDNs.keySet()) |
| | | { |
| | | long generationId=-1; |
| | | ReplicationServerDomain replicationServerDomain = |
| | |
| | | * @param baseDN The baseDN of the replicationServerDomain. |
| | | * @return The value of the generationID. |
| | | */ |
| | | public long getGenerationId(DN baseDN) |
| | | public long getGenerationId(String baseDN) |
| | | { |
| | | ReplicationServerDomain rsd = |
| | | this.getReplicationServerDomain(baseDN, false); |
| | |
| | | |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.protocol.AckMsg; |
| | | 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.MonitorMsg; |
| | | import org.opends.server.replication.protocol.MonitorRequestMsg; |
| | | import org.opends.server.replication.protocol.ResetGenerationIdMsg; |
| | | import org.opends.server.types.DN; |
| | | 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 |
| | |
| | | */ |
| | | public class ReplicationServerDomain |
| | | { |
| | | |
| | | private final Object flowControlLock = new Object(); |
| | | private final DN baseDn; |
| | | private final String baseDn; |
| | | // The Status analyzer that periodically verifis if the connected DSs are |
| | | // late or not |
| | | private StatusAnalyzer statusAnalyzer = null; |
| | |
| | | private MonitorData wrkMonitorData; |
| | | |
| | | /** |
| | | * The needed info for each received assured update message we are waiting |
| | | * acks for. |
| | | * Key: a change number matching a received update message which requested |
| | | * assured mode usage (either safe read or safe data mode) |
| | | * Value: The object holding every info needed about the already received acks |
| | | * as well as the acks to be received. |
| | | * For more details, see ExpectedAcksInfo and its sub classes javadoc. |
| | | */ |
| | | private final ConcurrentHashMap<ChangeNumber, ExpectedAcksInfo> waitingAcks = |
| | | new ConcurrentHashMap<ChangeNumber, ExpectedAcksInfo>(); |
| | | |
| | | // The timer used to run the timeout code (timer tasks) for the assured update |
| | | // messages we are waiting acks for. |
| | | private Timer assuredTimeoutTimer = null; |
| | | // Counter used to purge the timer tasks referemces in assuredTimeoutTimer, |
| | | // every n number of treated assured messages |
| | | private int assuredTimeoutTimerPurgeCounter = 0; |
| | | |
| | | /** |
| | | * Creates a new ReplicationServerDomain associated to the DN baseDn. |
| | | * |
| | | * @param baseDn The baseDn associated to the ReplicationServerDomain. |
| | | * @param replicationServer the ReplicationServer that created this |
| | | * replicationServer cache. |
| | | */ |
| | | public ReplicationServerDomain(DN baseDn, ReplicationServer replicationServer) |
| | | public ReplicationServerDomain( |
| | | String baseDn, ReplicationServer replicationServer) |
| | | { |
| | | this.baseDn = baseDn; |
| | | this.replicationServer = replicationServer; |
| | | this.assuredTimeoutTimer = new Timer("Replication Assured Timer for " + |
| | | baseDn + " in RS " + replicationServer.getServerId(), true); |
| | | } |
| | | |
| | | /** |
| | |
| | | public void put(UpdateMsg update, ServerHandler sourceHandler) |
| | | throws IOException |
| | | { |
| | | /* |
| | | * TODO : In case that the source server is a LDAP server this method |
| | | * should check that change did get pushed to at least one |
| | | * other replication server before pushing it to the LDAP servers |
| | | */ |
| | | |
| | | short id = update.getChangeNumber().getServerId(); |
| | | ChangeNumber cn = update.getChangeNumber(); |
| | | short id = cn.getServerId(); |
| | | sourceHandler.updateServerState(update); |
| | | sourceHandler.incrementInCount(); |
| | | |
| | | if (update.isAssured()) |
| | | { |
| | | int count = this.NumServers(); |
| | | if (count > 1) |
| | | { |
| | | if (sourceHandler.isReplicationServer()) |
| | | ServerHandler.addWaitingAck(update, sourceHandler.getServerId(), |
| | | this, count - 1); |
| | | else |
| | | sourceHandler.addWaitingAck(update, count - 1); |
| | | } else |
| | | { |
| | | sourceHandler.sendAck(update.getChangeNumber()); |
| | | } |
| | | } |
| | | |
| | | if (generationId < 0) |
| | | { |
| | | generationId = sourceHandler.getGenerationId(); |
| | |
| | | // Publish the messages to the source handler |
| | | dbHandler.add(update); |
| | | |
| | | /** |
| | | * If this is an assured message (a message requesting ack), we must |
| | | * construct the ExpectedAcksInfo object with the right number of expected |
| | | * acks before posting message to the writers. Otherwise some writers may |
| | | * have time to post, receive the ack and increment received ack counter |
| | | * (kept in ExpectedAcksInfo object) and we could think the acknowledgment |
| | | * is fully processed although it may be not (some other acks from other |
| | | * servers are not yet arrived). So for that purpose we do a pre-loop |
| | | * to determine to who we will post an assured message. |
| | | * Whether the assured mode is safe read or safe data, we anyway do not |
| | | * support the assured replication feature across topologies with different |
| | | * group ids. The assured feature insures assured replication based on the |
| | | * same locality (group id). For instance in double data center deployment |
| | | * (2 group id usage) with assured replication enabled, an assured message |
| | | * sent from data center 1 (group id = 1) will be sent to servers of both |
| | | * data centers, but one will request and wait acks only from servers of the |
| | | * data center 1. |
| | | */ |
| | | boolean assuredMessage = update.isAssured(); |
| | | PreparedAssuredInfo preparedAssuredInfo = null; |
| | | if (assuredMessage) |
| | | { |
| | | // Assured feature is supported starting from replication protocol V2 |
| | | if (sourceHandler.getProtocolVersion() >= |
| | | ProtocolVersion.REPLICATION_PROTOCOL_V2) |
| | | { |
| | | // According to assured sub-mode, prepare structures to keep track of |
| | | // the acks we are interested in. |
| | | AssuredMode assuredMode = update.getAssuredMode(); |
| | | if (assuredMode != AssuredMode.SAFE_DATA_MODE) |
| | | { |
| | | preparedAssuredInfo = processSafeDataUpdateMsg(update, sourceHandler); |
| | | } else if (assuredMode != AssuredMode.SAFE_READ_MODE) |
| | | { |
| | | preparedAssuredInfo = processSafeReadUpdateMsg(update, sourceHandler); |
| | | } else |
| | | { |
| | | // Unknown assured mode: should never happen |
| | | Message errorMsg = ERR_RS_UNKNOWN_ASSURED_MODE.get( |
| | | Short.toString(replicationServer.getServerId()), |
| | | assuredMode.toString(), baseDn, update.toString()); |
| | | logError(errorMsg); |
| | | assuredMessage = false; |
| | | } |
| | | } else |
| | | { |
| | | assuredMessage = false; |
| | | } |
| | | } |
| | | |
| | | List<Short> expectedServers = null; |
| | | if (assuredMessage) |
| | | { |
| | | expectedServers = preparedAssuredInfo.expectedServers; |
| | | if (expectedServers != null) |
| | | { |
| | | // Store the expected acks info into the global map. |
| | | // The code for processing reception of acks for this update will update |
| | | // info kept in this object and if enough acks received, it will send |
| | | // back the final ack to the requester and remove the object from this |
| | | // map |
| | | // OR |
| | | // The following timer will time out and send an timeout ack to the |
| | | // requester if the acks are not received in time. The timer will also |
| | | // remove the object from this map. |
| | | waitingAcks.put(cn, preparedAssuredInfo.expectedAcksInfo); |
| | | |
| | | // Arm timer for this assured update message (wait for acks until it |
| | | // times out) |
| | | AssuredTimeoutTask assuredTimeoutTask = new AssuredTimeoutTask(cn); |
| | | assuredTimeoutTimer.schedule(assuredTimeoutTask, |
| | | replicationServer.getAssuredTimeout()); |
| | | // Purge timer every 100 treated messages |
| | | assuredTimeoutTimerPurgeCounter++; |
| | | if ((assuredTimeoutTimerPurgeCounter % 100) == 0) |
| | | assuredTimeoutTimer.purge(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * The update message equivalent to the originally received update message, |
| | | * but with assured flag disabled. This message is the one that should be |
| | | * sent to non elligible servers for assured mode. |
| | | * We need a clone like of the original message with assured flag off, to be |
| | | * posted to servers we don't want to wait the ack from (not normal status |
| | | * servers or servers with different group id). This must be done because |
| | | * the posted message is a reference so each writer queue gets the same |
| | | * reference, thus, changing the assured flag of an object is done for every |
| | | * references posted on every writer queues. That is why we need a message |
| | | * version with assured flag on and another one with assured flag off. |
| | | */ |
| | | NotAssuredUpdateMsg notAssuredUpdate = null; |
| | | |
| | | /* |
| | | * Push the message to the replication servers |
| | | */ |
| | | if (!sourceHandler.isReplicationServer()) |
| | | if (sourceHandler.isLDAPserver()) |
| | | { |
| | | for (ServerHandler handler : replicationServers.values()) |
| | | { |
| | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("In RS " + |
| | | replicationServer.getServerId() + |
| | | " for dn " + baseDn.toNormalizedString() + ", update " + |
| | | " for dn " + baseDn + ", update " + |
| | | update.getChangeNumber().toString() + |
| | | " will not be sent to replication server " + |
| | | Short.toString(handler.getServerId()) + " with generation id " + |
| | |
| | | continue; |
| | | } |
| | | |
| | | handler.add(update, sourceHandler); |
| | | if (assuredMessage) |
| | | { |
| | | // Assured mode: post an assured or not assured matching update |
| | | // message according to what has been computed for the destination |
| | | // server |
| | | if ((expectedServers != null) && expectedServers.contains(handler. |
| | | getServerId())) |
| | | { |
| | | handler.add(update, sourceHandler); |
| | | } else |
| | | { |
| | | if (notAssuredUpdate == null) |
| | | { |
| | | notAssuredUpdate = new NotAssuredUpdateMsg(update); |
| | | } |
| | | handler.add(notAssuredUpdate, sourceHandler); |
| | | } |
| | | } else |
| | | { |
| | | handler.add(update, sourceHandler); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | for (ServerHandler handler : directoryServers.values()) |
| | | { |
| | | // don't forward the change to the server that just sent it |
| | | // Don't forward the change to the server that just sent it |
| | | if (handler == sourceHandler) |
| | | { |
| | | continue; |
| | |
| | | if (dsStatus == ServerStatus.BAD_GEN_ID_STATUS) |
| | | TRACER.debugInfo("In RS " + |
| | | replicationServer.getServerId() + |
| | | " for dn " + baseDn.toNormalizedString() + ", update " + |
| | | " for dn " + baseDn + ", update " + |
| | | update.getChangeNumber().toString() + |
| | | " will not be sent to directory server " + |
| | | Short.toString(handler.getServerId()) + " with generation id " + |
| | |
| | | if (dsStatus == ServerStatus.FULL_UPDATE_STATUS) |
| | | TRACER.debugInfo("In RS " + |
| | | replicationServer.getServerId() + |
| | | " for dn " + baseDn.toNormalizedString() + ", update " + |
| | | " for dn " + baseDn + ", update " + |
| | | update.getChangeNumber().toString() + |
| | | " will not be sent to directory server " + |
| | | Short.toString(handler.getServerId()) + |
| | |
| | | continue; |
| | | } |
| | | |
| | | handler.add(update, sourceHandler); |
| | | if (assuredMessage) |
| | | { |
| | | // Assured mode: post an assured or not assured matching update |
| | | // message according to what has been computed for the destination |
| | | // server |
| | | if ((expectedServers != null) && expectedServers.contains(handler. |
| | | getServerId())) |
| | | { |
| | | handler.add(update, sourceHandler); |
| | | } else |
| | | { |
| | | if (notAssuredUpdate == null) |
| | | { |
| | | notAssuredUpdate = new NotAssuredUpdateMsg(update); |
| | | } |
| | | handler.add(notAssuredUpdate, sourceHandler); |
| | | } |
| | | } else |
| | | { |
| | | handler.add(update, sourceHandler); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Helper class to be the return type of a method that processes a just |
| | | * received assured update message: |
| | | * - processSafeReadUpdateMsg |
| | | * - processSafeDataUpdateMsg |
| | | * This is a facility to pack many interesting returned object. |
| | | */ |
| | | private class PreparedAssuredInfo |
| | | { |
| | | /** |
| | | * The list of servers identified as servers we are interested in |
| | | * receiving acks from. If this list is not null, then expectedAcksInfo |
| | | * should be not null. |
| | | * Servers that are not in this list are servers not elligible for an ack |
| | | * request. |
| | | * |
| | | */ |
| | | public List<Short> expectedServers = null; |
| | | |
| | | /** |
| | | * The constructed ExpectedAcksInfo object to be used when acks will be |
| | | * received. Null if expectedServers is null. |
| | | */ |
| | | public ExpectedAcksInfo expectedAcksInfo = null; |
| | | } |
| | | |
| | | /** |
| | | * Process a just received assured update message in Safe Read mode. If the |
| | | * ack can be sent immediately, it is done here. This will also determine to |
| | | * which suitable servers an ack should be requested from, and which ones are |
| | | * not elligible for an ack request. |
| | | * This method is an helper method for the put method. Have a look at the put |
| | | * method for a better understanding. |
| | | * @param update The just received assured update to process. |
| | | * @param sourceHandler The ServerHandler for the server from which the |
| | | * update was received |
| | | * @return A suitable PreparedAssuredInfo object that contains every needed |
| | | * info to proceed with post to server writers. |
| | | * @throws IOException When an IO exception happens during the update |
| | | * processing. |
| | | */ |
| | | private PreparedAssuredInfo processSafeReadUpdateMsg( |
| | | UpdateMsg update, ServerHandler sourceHandler) throws IOException |
| | | { |
| | | ChangeNumber cn = update.getChangeNumber(); |
| | | byte groupId = replicationServer.getGroupId(); |
| | | byte sourceGroupId = sourceHandler.getGroupId(); |
| | | List<Short> expectedServers = new ArrayList<Short>(); |
| | | List<Short> wrongStatusServers = new ArrayList<Short>(); |
| | | |
| | | if (sourceGroupId != groupId) |
| | | // Assured feature does not cross different group ids |
| | | { |
| | | if (sourceHandler.isLDAPserver()) |
| | | { |
| | | // Look for RS elligible for assured |
| | | for (ServerHandler handler : replicationServers.values()) |
| | | { |
| | | if (handler.getGroupId() == groupId) |
| | | expectedServers.add(handler.getServerId()); |
| | | } |
| | | } |
| | | |
| | | // Look for DS elligible for assured |
| | | for (ServerHandler handler : directoryServers.values()) |
| | | { |
| | | // Don't forward the change to the server that just sent it |
| | | if (handler == sourceHandler) |
| | | { |
| | | continue; |
| | | } |
| | | if (handler.getGroupId() == groupId) |
| | | { |
| | | if (handler.getStatus() == ServerStatus.NORMAL_STATUS) |
| | | { |
| | | expectedServers.add(handler.getServerId()); |
| | | } else |
| | | { |
| | | wrongStatusServers.add(handler.getServerId()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Return computed structures |
| | | PreparedAssuredInfo preparedAssuredInfo = new PreparedAssuredInfo(); |
| | | if (expectedServers.size() > 0) |
| | | { |
| | | // Some other acks to wait for |
| | | preparedAssuredInfo.expectedAcksInfo = new SafeReadExpectedAcksInfo(cn, |
| | | sourceHandler, expectedServers, wrongStatusServers); |
| | | preparedAssuredInfo.expectedServers = expectedServers; |
| | | } |
| | | |
| | | if (preparedAssuredInfo.expectedServers == null) |
| | | { |
| | | // No elligible servers found, send the ack immediatly |
| | | AckMsg ack = new AckMsg(cn); |
| | | sourceHandler.sendAck(ack); |
| | | } |
| | | |
| | | return preparedAssuredInfo; |
| | | } |
| | | |
| | | /** |
| | | * Process a just received assured update message in Safe Data mode. If the |
| | | * ack can be sent immediately, it is done here. This will also determine to |
| | | * which suitable servers an ack should be requested from, and which ones are |
| | | * not elligible for an ack request. |
| | | * This method is an helper method for the put method. Have a look at the put |
| | | * method for a better understanding. |
| | | * @param update The just received assured update to process. |
| | | * @param sourceHandler The ServerHandler for the server from which the |
| | | * update was received |
| | | * @return A suitable PreparedAssuredInfo object that contains every needed |
| | | * info to proceed with post to server writers. |
| | | * @throws IOException When an IO exception happens during the update |
| | | * processing. |
| | | */ |
| | | private PreparedAssuredInfo processSafeDataUpdateMsg( |
| | | UpdateMsg update, ServerHandler sourceHandler) throws IOException |
| | | { |
| | | ChangeNumber cn = update.getChangeNumber(); |
| | | boolean interestedInAcks = true; |
| | | byte safeDataLevel = update.getSafeDataLevel(); |
| | | byte groupId = replicationServer.getGroupId(); |
| | | byte sourceGroupId = sourceHandler.getGroupId(); |
| | | if (safeDataLevel < (byte) 1) |
| | | { |
| | | // Should never happen |
| | | Message errorMsg = ERR_UNKNOWN_ASSURED_SAFE_DATA_LEVEL.get( |
| | | Short.toString(replicationServer.getServerId()), |
| | | Byte.toString(safeDataLevel), baseDn, update.toString()); |
| | | logError(errorMsg); |
| | | interestedInAcks = false; |
| | | } else if (sourceGroupId != groupId) |
| | | { |
| | | // Assured feature does not cross different group ids |
| | | interestedInAcks = false; |
| | | } else |
| | | { |
| | | if (sourceHandler.isLDAPserver()) |
| | | { |
| | | if (safeDataLevel == (byte) 1) |
| | | { |
| | | // Immediatly return the ack for an assured message in safe data mode |
| | | // with safe data level 1, coming from a DS. No need to wait for more |
| | | // acks |
| | | AckMsg ack = new AckMsg(cn); |
| | | sourceHandler.sendAck(ack); |
| | | interestedInAcks = false; // No further acks to obtain |
| | | } else |
| | | { |
| | | // level > 1 : We need further acks |
| | | // The message will be posted in assured mode to elligible servers. |
| | | // The embedded safe data level is not changed, and his value will be |
| | | // used by a remote RS to determine if he must send an ack (level > 1) |
| | | // or not (level = 1) |
| | | } |
| | | } else |
| | | { // A RS sent us the safe data message, for sure no futher acks to wait |
| | | interestedInAcks = false; |
| | | if (safeDataLevel == (byte) 1) |
| | | { |
| | | // The original level was 1 so the RS that sent us this message should |
| | | // have already sent his ack to the sender DS. Level 1 has already |
| | | // been reached so no further acks to wait |
| | | // This should not happen in theory as the sender RS server should |
| | | // have sent us a matching not assured message so we should not come |
| | | // to here. |
| | | } else |
| | | { |
| | | // level > 1, so Ack this message to originator RS |
| | | AckMsg ack = new AckMsg(cn); |
| | | sourceHandler.sendAck(ack); |
| | | } |
| | | } |
| | | } |
| | | |
| | | List<Short> expectedServers = new ArrayList<Short>(); |
| | | if (interestedInAcks) |
| | | { |
| | | if (sourceHandler.isLDAPserver()) |
| | | { |
| | | // Look for RS elligible for assured |
| | | for (ServerHandler handler : replicationServers.values()) |
| | | { |
| | | if (handler.getGroupId() == groupId) |
| | | expectedServers.add(handler.getServerId()); |
| | | } |
| | | } |
| | | |
| | | // Look for DS elligible for assured |
| | | for (ServerHandler handler : directoryServers.values()) |
| | | { |
| | | // Don't forward the change to the server that just sent it |
| | | if (handler == sourceHandler) |
| | | { |
| | | continue; |
| | | } |
| | | if (handler.getGroupId() == groupId) |
| | | expectedServers.add(handler.getServerId()); |
| | | } |
| | | } |
| | | |
| | | // Return computed structures |
| | | PreparedAssuredInfo preparedAssuredInfo = new PreparedAssuredInfo(); |
| | | if (interestedInAcks && (expectedServers.size() > 0)) |
| | | { |
| | | // Some other acks to wait for |
| | | preparedAssuredInfo.expectedAcksInfo = new SafeDataExpectedAcksInfo(cn, |
| | | sourceHandler, update.getSafeDataLevel()); |
| | | preparedAssuredInfo.expectedServers = expectedServers; |
| | | } |
| | | |
| | | if (interestedInAcks && (preparedAssuredInfo.expectedServers == null)) |
| | | { |
| | | // level > 1 and source is a DS but no elligible servers found, send the |
| | | // ack immediatly |
| | | AckMsg ack = new AckMsg(cn); |
| | | sourceHandler.sendAck(ack); |
| | | } |
| | | |
| | | return preparedAssuredInfo; |
| | | } |
| | | |
| | | /** |
| | | * Process an ack received from a given server. |
| | | * |
| | | * @param ack The ack message received. |
| | | * @param ackingServer The server handler of the server that sent the ack. |
| | | */ |
| | | public void processAck(AckMsg ack, ServerHandler ackingServer) |
| | | { |
| | | // Retrieve the expected acks info for the update matching the original |
| | | // sent update. |
| | | ChangeNumber cn = ack.getChangeNumber(); |
| | | ExpectedAcksInfo expectedAcksInfo = waitingAcks.get(cn); |
| | | |
| | | if (expectedAcksInfo != null) |
| | | { |
| | | // Prevent concurrent access from processAck() or AssuredTimeoutTask.run() |
| | | synchronized (expectedAcksInfo) |
| | | { |
| | | if (expectedAcksInfo.isCompleted()) |
| | | { |
| | | // Timeout code is sending a timeout ack, do nothing and let him |
| | | // remove object from the map |
| | | return; |
| | | } |
| | | // If this is the last ack we were waiting from, immediatly create and |
| | | // send the final ack to the original server |
| | | if (expectedAcksInfo.processReceivedAck(ackingServer, ack)) |
| | | { |
| | | // Remove the object from the map as no more needed |
| | | waitingAcks.remove(cn); |
| | | AckMsg finalAck = expectedAcksInfo.createAck(false); |
| | | ServerHandler origServer = expectedAcksInfo.getRequesterServer(); |
| | | try |
| | | { |
| | | origServer.sendAck(finalAck); |
| | | } catch (IOException e) |
| | | { |
| | | /* |
| | | * An error happened trying the send back an ack to the server. |
| | | * Log an error and close the connection to this server. |
| | | */ |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(ERR_RS_ERROR_SENDING_ACK.get( |
| | | Short.toString(replicationServer.getServerId()), |
| | | Short.toString(origServer.getServerId()), cn.toString(), baseDn)); |
| | | mb.append(stackTraceToSingleLineString(e)); |
| | | logError(mb.toMessage()); |
| | | stopServer(origServer); |
| | | } |
| | | // Mark the ack info object as completed to prevent potential timeout |
| | | // code parallel run |
| | | expectedAcksInfo.completed(); |
| | | } |
| | | } |
| | | } else |
| | | { |
| | | // The timeout occured for the update matching this change number and the |
| | | // ack with timeout error has probably already been sent. |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * The code run when the timeout occurs while waiting for acks of the |
| | | * elligible servers. This basically sends a timeout ack (with any additional |
| | | * error info) to the original server that sent an assured update message. |
| | | */ |
| | | private class AssuredTimeoutTask extends TimerTask |
| | | { |
| | | private ChangeNumber cn = null; |
| | | |
| | | /** |
| | | * Constructor for the timer task. |
| | | * @param cn The changenumber of the assured update we are waiting acks for |
| | | */ |
| | | public AssuredTimeoutTask(ChangeNumber cn) |
| | | { |
| | | this.cn = cn; |
| | | } |
| | | |
| | | /** |
| | | * Run when the assured timeout for an assured update message we are waiting |
| | | * acks for occurs. |
| | | */ |
| | | public void run() |
| | | { |
| | | ExpectedAcksInfo expectedAcksInfo = waitingAcks.get(cn); |
| | | |
| | | if (expectedAcksInfo != null) |
| | | { |
| | | synchronized (expectedAcksInfo) |
| | | { |
| | | if (expectedAcksInfo.isCompleted()) |
| | | { |
| | | // processAck() code is sending the ack, do nothing and let him |
| | | // remove object from the map |
| | | return; |
| | | } |
| | | // Remove the object from the map as no more needed |
| | | waitingAcks.remove(cn); |
| | | // Create the timeout ack and send him to the server the assured |
| | | // update message came from |
| | | AckMsg finalAck = expectedAcksInfo.createAck(true); |
| | | ServerHandler origServer = expectedAcksInfo.getRequesterServer(); |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | | "In RS " + Short.toString(replicationServer.getServerId()) + |
| | | " for " + baseDn + |
| | | ", sending timeout for assured update with change " + " number " + |
| | | cn.toString() + " to server id " + |
| | | Short.toString(origServer.getServerId())); |
| | | try |
| | | { |
| | | origServer.sendAck(finalAck); |
| | | } catch (IOException e) |
| | | { |
| | | /* |
| | | * An error happened trying the send back an ack to the server. |
| | | * Log an error and close the connection to this server. |
| | | */ |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(ERR_RS_ERROR_SENDING_ACK.get( |
| | | Short.toString(replicationServer.getServerId()), |
| | | Short.toString(origServer.getServerId()), cn.toString(), baseDn)); |
| | | mb.append(stackTraceToSingleLineString(e)); |
| | | logError(mb.toMessage()); |
| | | stopServer(origServer); |
| | | } |
| | | // Mark the ack info object as completed to prevent potential |
| | | // processAck() code parallel run |
| | | expectedAcksInfo.completed(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * Get the baseDn. |
| | | * @return Returns the baseDn. |
| | | */ |
| | | public DN getBaseDn() |
| | | public String getBaseDn() |
| | | { |
| | | return baseDn; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the number of currently connected servers. |
| | | * |
| | | * @return the number of currently connected servers. |
| | | */ |
| | | private int NumServers() |
| | | { |
| | | return replicationServers.size() + directoryServers.size(); |
| | | } |
| | | |
| | | /** |
| | | * Add an ack to the list of ack received for a given change. |
| | | * |
| | | * @param message The ack message received. |
| | | * @param fromServerId The identifier of the server that sent the ack. |
| | | */ |
| | | public void ack(AckMsg message, short fromServerId) |
| | | { |
| | | /* |
| | | * there are 2 possible cases here : |
| | | * - the message that was acked comes from a server to which |
| | | * we are directly connected. |
| | | * In this case, we can find the handler from the directoryServers map |
| | | * - the message that was acked comes from a server to which we are not |
| | | * connected. |
| | | * In this case we need to find the replication server that forwarded |
| | | * the change and send back the ack to this server. |
| | | */ |
| | | ServerHandler handler = directoryServers.get( |
| | | message.getChangeNumber().getServerId()); |
| | | if (handler != null) |
| | | handler.ack(message, fromServerId); |
| | | else |
| | | { |
| | | ServerHandler.ackChangelog(message, fromServerId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the destination handlers for a routable message. |
| | | * |
| | | * @param msg The message to route. |
| | |
| | | } |
| | | |
| | | /** |
| | | * Send back an ack to the server that sent the change. |
| | | * |
| | | * @param changeNumber The ChangeNumber of the change that must be acked. |
| | | * @param isLDAPserver This boolean indicates if the server that sent the |
| | | * change was an LDAP server or a ReplicationServer. |
| | | */ |
| | | public void sendAck(ChangeNumber changeNumber, boolean isLDAPserver) |
| | | { |
| | | short serverId = changeNumber.getServerId(); |
| | | sendAck(changeNumber, isLDAPserver, serverId); |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * Send back an ack to a server that sent the change. |
| | | * |
| | | * @param changeNumber The ChangeNumber of the change that must be acked. |
| | | * @param isLDAPserver This boolean indicates if the server that sent the |
| | | * change was an LDAP server or a ReplicationServer. |
| | | * @param serverId The identifier of the server from which we |
| | | * received the change.. |
| | | */ |
| | | public void sendAck(ChangeNumber changeNumber, boolean isLDAPserver, |
| | | short serverId) |
| | | { |
| | | ServerHandler handler; |
| | | if (isLDAPserver) |
| | | handler = directoryServers.get(serverId); |
| | | else |
| | | handler = replicationServers.get(serverId); |
| | | |
| | | // TODO : check for null handler and log error |
| | | try |
| | | { |
| | | handler.sendAck(changeNumber); |
| | | } catch (IOException e) |
| | | { |
| | | /* |
| | | * An error happened trying the send back an ack to this server. |
| | | * Log an error and close the connection to this server. |
| | | */ |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(ERR_CHANGELOG_ERROR_SENDING_ACK.get(this.toString())); |
| | | mb.append(stackTraceToSingleLineString(e)); |
| | | logError(mb.toMessage()); |
| | | stopServer(handler); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Shutdown this ReplicationServerDomain. |
| | | */ |
| | | public void shutdown() |
| | |
| | | { |
| | | // Put RS info |
| | | rsInfos.add(serverHandler.toRSInfo()); |
| | | // Put his DSs info |
| | | Map<Short, LightweightServerHandler> lsList = |
| | | serverHandler.getConnectedDSs(); |
| | | for (LightweightServerHandler ls : lsList.values()) |
| | | { |
| | | dsInfos.add(ls.toDSInfo()); |
| | | } |
| | | |
| | | serverHandler.addDSInfos(dsInfos); |
| | | } |
| | | |
| | | return new TopologyMsg(dsInfos, rsInfos); |
| | |
| | | if (generationId > 0 && (generationId != handler.getGenerationId())) |
| | | { |
| | | Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get( |
| | | baseDn.toNormalizedString(), |
| | | baseDn, |
| | | Short.toString(handler.getServerId()), |
| | | Long.toString(handler.getGenerationId()), |
| | | Long.toString(generationId)); |
| | |
| | | synchronized (wrkMonitorData) |
| | | { |
| | | // Here is the RS state : list <serverID, lastChangeNumber> |
| | | // For each LDAP Server, we keep the max CN accross the RSes |
| | | // For each LDAP Server, we keep the max CN across the RSes |
| | | ServerState replServerState = msg.getReplServerDbState(); |
| | | wrkMonitorData.setMaxCNs(replServerState); |
| | | |
| | | // store the remote RS states. |
| | | wrkMonitorData.setRSState(msg.getsenderID(), replServerState); |
| | | |
| | | // Store the remote LDAP servers states |
| | | Iterator<Short> lsidIterator = msg.ldapIterator(); |
| | | while (lsidIterator.hasNext()) |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | |
| | | package org.opends.server.replication.server; |
| | | |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | |
| | | 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.protocol.AckMsg; |
| | | |
| | | /** |
| | | * This class holds every info needed about the expected acks for a received |
| | | * update message requesting assured replication with Safe Data sub-mode. |
| | | * It also includes info/routines for constructing the final ack to be sent to |
| | | * the sender of the update message. |
| | | */ |
| | | public class SafeDataExpectedAcksInfo extends ExpectedAcksInfo |
| | | { |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | // Requested level of safe data when the update message was received. |
| | | private byte safeDataLevel = (byte)-1; |
| | | |
| | | // Number of received acks for the matching update message, up to now |
| | | // Already set to 1 as the local RS receiving the message from a DS counts. |
| | | private byte numReceivedAcks = (byte)1; |
| | | |
| | | /** |
| | | * Creates a new SafeDataExpectedAcksInfo. |
| | | * @param changeNumber The change number of the assured update message |
| | | * @param requesterServerHandler The server that sent the assured update |
| | | * message |
| | | * @param safeDataLevel The Safe Data level requested for the assured |
| | | * update message |
| | | */ |
| | | public SafeDataExpectedAcksInfo(ChangeNumber changeNumber, |
| | | ServerHandler requesterServerHandler, byte safeDataLevel) |
| | | { |
| | | super(changeNumber, requesterServerHandler, AssuredMode.SAFE_DATA_MODE); |
| | | this.safeDataLevel = safeDataLevel; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean processReceivedAck(ServerHandler ackingServer, AckMsg ackMsg) |
| | | { |
| | | /* |
| | | * Security: although a DS should not respond to an update message sent to |
| | | * 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()) |
| | | { |
| | | // Sanity check: this should never happen |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Received unexpected SD ack from DS id: " |
| | | + ackingServer.getServerId() + " ack message: " + ackMsg); |
| | | return false; |
| | | } |
| | | |
| | | numReceivedAcks++; |
| | | if (numReceivedAcks == safeDataLevel) |
| | | return true; |
| | | else |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public AckMsg createAck(boolean timeout) |
| | | { |
| | | AckMsg ack = new AckMsg(changeNumber); |
| | | |
| | | if (timeout) |
| | | ack.setHasTimeout(true); |
| | | |
| | | return ack; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | |
| | | package org.opends.server.replication.server; |
| | | |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import org.opends.server.replication.common.AssuredMode; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.protocol.AckMsg; |
| | | |
| | | /** |
| | | * This class holds every info needed about the expected acks for a received |
| | | * update message requesting assured replication with Safe Read sub-mode. |
| | | * It also includes info/routines for constructing the final ack to be sent to |
| | | * the sender of the update message. |
| | | */ |
| | | public class SafeReadExpectedAcksInfo extends ExpectedAcksInfo |
| | | { |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | // Did some servers go in timeout when the matching update was sent ? |
| | | private boolean hasTimeout = false; |
| | | |
| | | // Were some servers in wrong status when the matching update was sent ? |
| | | private boolean hasWrongStatus = false; |
| | | |
| | | // Did some servers make an error replaying the sent matching update ? |
| | | private boolean hasReplayError = false; |
| | | |
| | | // The list of server ids that had errors for the sent matching update |
| | | // Each server id of the list had one of the |
| | | // 3 possible errors (timeout, wrong status or replay error) |
| | | private List<Short> failedServers = null; |
| | | |
| | | /** |
| | | * This gives the list of servers we are willing to wait acks from and the |
| | | * information about the ack from the servers. |
| | | * key: the id of the server. |
| | | * value: a boolean true if we received the ack from the server, |
| | | * false otherwise. |
| | | * This must not include servers we already identified they are in wrong |
| | | * status, but just servers that are in normal status. |
| | | */ |
| | | private Map<Short,Boolean> expectedServersAckStatus = |
| | | new HashMap<Short,Boolean>(); |
| | | |
| | | /** |
| | | * Number of servers we want an ack from and from which we received the ack. |
| | | * Said differently: the number of servers in expectedServersAckStatus whose |
| | | * value is true. When this value reaches the size of expectedServersAckStatus |
| | | * we can compute an ack message (based on info in this object), to be |
| | | * returned to the (requester) server that sent us an assured update message. |
| | | */ |
| | | private int numKnownAckStatus = 0; |
| | | |
| | | /** |
| | | * Creates a new SafeReadExpectedAcksInfo. |
| | | * @param changeNumber The change number of the assured update message |
| | | * @param requesterServerHandler The server that sent the assured update |
| | | * message |
| | | * @param expectedServers The list of servers we want an ack from (they are |
| | | * in normal status and have the same group id as us) |
| | | * @param wrongStatusServers The list of all servers already detected in |
| | | * wrongStatus (degraded status) to keep trace of the error for the future |
| | | * returning ack we gonna compute |
| | | */ |
| | | public SafeReadExpectedAcksInfo(ChangeNumber changeNumber, |
| | | ServerHandler requesterServerHandler, List<Short> expectedServers, |
| | | List<Short> wrongStatusServers) |
| | | { |
| | | super(changeNumber, requesterServerHandler, AssuredMode.SAFE_READ_MODE); |
| | | |
| | | // Keep track of potential servers detected in wrong status |
| | | if (wrongStatusServers.size() > 0) |
| | | { |
| | | hasWrongStatus = true; |
| | | failedServers = wrongStatusServers; |
| | | } |
| | | |
| | | // Initialize list of servers we expect acks from |
| | | for (Short serverId : expectedServers) |
| | | { |
| | | expectedServersAckStatus.put(serverId, false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Sets the timeout marker for the future update ack. |
| | | * @param hasTimeout True if some timeout occurred |
| | | */ |
| | | public void setHasTimeout(boolean hasTimeout) |
| | | { |
| | | this.hasTimeout = hasTimeout; |
| | | } |
| | | |
| | | /** |
| | | * Sets the wrong status marker for the future update ack. |
| | | * @param hasWrongStatus True if some servers were in wrong status |
| | | */ |
| | | public void setHasWrongStatus(boolean hasWrongStatus) |
| | | { |
| | | this.hasWrongStatus = hasWrongStatus; |
| | | } |
| | | |
| | | /** |
| | | * Sets the replay error marker for the future update ack. |
| | | * @param hasReplayError True if some servers had errors replaying the change |
| | | */ |
| | | public void setHasReplayError(boolean hasReplayError) |
| | | { |
| | | this.hasReplayError = hasReplayError; |
| | | } |
| | | |
| | | /** |
| | | * Gets the timeout marker for the future update ack. |
| | | * @return The timeout marker for the future update ack. |
| | | */ |
| | | public boolean hasTimeout() |
| | | { |
| | | return hasTimeout; |
| | | } |
| | | |
| | | /** |
| | | * Gets the wrong status marker for the future update ack. |
| | | * @return hasWrongStatus The wrong status marker for the future update ack. |
| | | */ |
| | | public boolean hasWrongStatus() |
| | | { |
| | | return hasWrongStatus; |
| | | } |
| | | |
| | | /** |
| | | * Gets the replay error marker for the future update ack. |
| | | * @return hasReplayError The replay error marker for the future update ack. |
| | | */ |
| | | public boolean hasReplayError() |
| | | { |
| | | return hasReplayError; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean processReceivedAck(ServerHandler ackingServer, AckMsg ackMsg) |
| | | { |
| | | // Get the ack status for the matching server |
| | | short ackingServerId = ackingServer.getServerId(); |
| | | boolean ackReceived = expectedServersAckStatus.get(ackingServerId); |
| | | if (ackReceived) |
| | | { |
| | | // Sanity check: this should never happen |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Received unexpected ack from server id: " |
| | | + ackingServerId + " ack message: " + ackMsg); |
| | | return false; |
| | | } else |
| | | { |
| | | // Analyze received ack and update info for the ack to be later computed |
| | | // accordingly |
| | | boolean someErrors = false; |
| | | if (ackMsg.hasTimeout()) |
| | | { |
| | | hasTimeout = true; |
| | | someErrors = true; |
| | | } |
| | | if (ackMsg.hasWrongStatus()) |
| | | { |
| | | hasWrongStatus = true; |
| | | someErrors = true; |
| | | } |
| | | if (ackMsg.hasReplayError()) |
| | | { |
| | | hasReplayError = true; |
| | | someErrors = true; |
| | | } |
| | | if (someErrors) |
| | | { |
| | | failedServers.addAll(ackMsg.getFailedServers()); |
| | | } |
| | | |
| | | // Mark this ack received for the server |
| | | expectedServersAckStatus.put(ackingServerId, true); |
| | | numKnownAckStatus++; |
| | | } |
| | | |
| | | return (numKnownAckStatus == expectedServersAckStatus.size()); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public AckMsg createAck(boolean timeout) |
| | | { |
| | | AckMsg ack = new AckMsg(changeNumber); |
| | | |
| | | // Fill collected errors info |
| | | ack.setHasTimeout(hasTimeout); |
| | | ack.setHasWrongStatus(hasWrongStatus); |
| | | ack.setHasReplayError(hasReplayError); |
| | | ack.setFailedServers(failedServers); |
| | | |
| | | // Force anyway timeout flag if requested |
| | | if (timeout) |
| | | ack.setHasTimeout(true); |
| | | |
| | | return ack; |
| | | } |
| | | } |
| | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | import java.util.Random; |
| | | import java.util.Set; |
| | |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeBuilder; |
| | | import org.opends.server.types.Attributes; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.util.TimeThread; |
| | | |
| | |
| | | private ProtocolSession session; |
| | | private final MsgQueue msgQueue = new MsgQueue(); |
| | | private MsgQueue lateQueue = new MsgQueue(); |
| | | private final Map<ChangeNumber, AckMessageList> waitingAcks = |
| | | new HashMap<ChangeNumber, AckMessageList>(); |
| | | private ReplicationServerDomain replicationServerDomain = null; |
| | | private String serverURL; |
| | | private int outCount = 0; // number of update sent to the server |
| | | |
| | | private int inCount = 0; // number of updates received from the server |
| | | |
| | | private int inAckCount = 0; |
| | | private int outAckCount = 0; |
| | | private int maxReceiveQueue = 0; |
| | | private int maxSendQueue = 0; |
| | | private int maxReceiveDelay = 0; |
| | |
| | | private ServerState serverState; |
| | | private boolean activeWriter = true; |
| | | private ServerWriter writer = null; |
| | | private DN baseDn = null; |
| | | private String baseDn = null; |
| | | private int rcvWindow; |
| | | private int rcvWindowSizeHalf; |
| | | private int maxRcvWindow; |
| | |
| | | * Properties filled only if remote server is a DS |
| | | */ |
| | | |
| | | // Status of this 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>(); |
| | |
| | | * Set when ServerHandler is stopping. |
| | | */ |
| | | private AtomicBoolean shuttingDown = new AtomicBoolean(false); |
| | | private static final Map<ChangeNumber, ReplServerAckMessageList> |
| | | changelogsWaitingAcks = |
| | | new HashMap<ChangeNumber, ReplServerAckMessageList>(); |
| | | |
| | | /** |
| | | * Creates a new server handler instance with the provided socket. |
| | |
| | | * @param replicationServer the ReplicationServer that created this server |
| | | * handler. |
| | | */ |
| | | public void start(DN baseDn, short replicationServerId, |
| | | public void start(String baseDn, short replicationServerId, |
| | | String replicationServerURL, |
| | | int windowSize, boolean sslEncryption, |
| | | ReplicationServer replicationServer) |
| | |
| | | { |
| | | // Timeout |
| | | Message message = NOTE_TIMEOUT_WHEN_CROSS_CONNECTION.get( |
| | | this.baseDn.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Short.toString(replicationServer.getServerId())); |
| | | closeSession(message); |
| | |
| | | // 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.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Long.toString(generationId), |
| | | Long.toString(localGenerationId)); |
| | |
| | | // replicationServerDomain. |
| | | // setGenerationId(generationId, false); |
| | | Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get( |
| | | this.baseDn.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Long.toString(generationId), |
| | | Long.toString(localGenerationId)); |
| | |
| | | if (generationId != localGenerationId) |
| | | { |
| | | Message message = NOTE_BAD_GENERATION_ID_FROM_DS.get( |
| | | this.baseDn.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Long.toString(generationId), |
| | | Long.toString(localGenerationId)); |
| | |
| | | // 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.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Long.toString(generationId), |
| | | Long.toString(localGenerationId)); |
| | |
| | | // 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.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Long.toString(generationId), |
| | | Long.toString(localGenerationId)); |
| | |
| | | // replicationServerDomain. |
| | | // setGenerationId(generationId, false); |
| | | Message message = NOTE_BAD_GENERATION_ID_FROM_RS.get( |
| | | this.baseDn.toNormalizedString(), |
| | | this.baseDn, |
| | | Short.toString(serverId), |
| | | Long.toString(generationId), |
| | | Long.toString(localGenerationId)); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the number of Ack received from the server managed by this handler. |
| | | * |
| | | * @return Returns the inAckCount. |
| | | */ |
| | | public int getInAckCount() |
| | | { |
| | | return inAckCount; |
| | | } |
| | | |
| | | /** |
| | | * Get the number of Ack sent to the server managed by this handler. |
| | | * |
| | | * @return Returns the outAckCount. |
| | | */ |
| | | public int getOutAckCount() |
| | | { |
| | | return outAckCount; |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | } |
| | | |
| | | /** |
| | | * Send the ack to the server that did the original modification. |
| | | * Sends an ack message to the server represented by this object. |
| | | * |
| | | * @param changeNumber The ChangeNumber of the update that is acked. |
| | | * @param ack The ack message to be sent. |
| | | * @throws IOException In case of Exception thrown sending the ack. |
| | | */ |
| | | public void sendAck(ChangeNumber changeNumber) throws IOException |
| | | public void sendAck(AckMsg ack) throws IOException |
| | | { |
| | | AckMsg ack = new AckMsg(changeNumber); |
| | | session.publish(ack); |
| | | outAckCount++; |
| | | } |
| | | |
| | | /** |
| | | * Do the work when an ack message has been received from another server. |
| | | * |
| | | * @param message The ack message that was received. |
| | | * @param ackingServerId The id of the server that acked the change. |
| | | */ |
| | | public void ack(AckMsg message, short ackingServerId) |
| | | { |
| | | ChangeNumber changeNumber = message.getChangeNumber(); |
| | | AckMessageList ackList; |
| | | boolean completedFlag; |
| | | synchronized (waitingAcks) |
| | | { |
| | | ackList = waitingAcks.get(changeNumber); |
| | | if (ackList == null) |
| | | return; |
| | | ackList.addAck(ackingServerId); |
| | | completedFlag = ackList.completed(); |
| | | if (completedFlag) |
| | | { |
| | | waitingAcks.remove(changeNumber); |
| | | } |
| | | } |
| | | if (completedFlag) |
| | | { |
| | | replicationServerDomain.sendAck(changeNumber, true); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Process reception of an for an update that was received from a |
| | | * ReplicationServer. |
| | | * |
| | | * @param message the ack message that was received. |
| | | * @param ackingServerId The id of the server that acked the change. |
| | | */ |
| | | public static void ackChangelog(AckMsg message, short ackingServerId) |
| | | { |
| | | ChangeNumber changeNumber = message.getChangeNumber(); |
| | | ReplServerAckMessageList ackList; |
| | | boolean completedFlag; |
| | | synchronized (changelogsWaitingAcks) |
| | | { |
| | | ackList = changelogsWaitingAcks.get(changeNumber); |
| | | if (ackList == null) |
| | | return; |
| | | ackList.addAck(ackingServerId); |
| | | completedFlag = ackList.completed(); |
| | | if (completedFlag) |
| | | { |
| | | changelogsWaitingAcks.remove(changeNumber); |
| | | } |
| | | } |
| | | if (completedFlag) |
| | | { |
| | | ReplicationServerDomain replicationServerDomain = |
| | | ackList.getChangelogCache(); |
| | | replicationServerDomain.sendAck(changeNumber, false, |
| | | ackList.getReplicationServerId()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Add an update to the list of update waiting for acks. |
| | | * |
| | | * @param update the update that must be added to the list |
| | | * @param nbWaitedAck The number of ack that must be received before |
| | | * the update is fully acked. |
| | | */ |
| | | public void addWaitingAck(UpdateMsg update, int nbWaitedAck) |
| | | { |
| | | AckMessageList ackList = new AckMessageList(update.getChangeNumber(), |
| | | nbWaitedAck); |
| | | synchronized (waitingAcks) |
| | | { |
| | | waitingAcks.put(update.getChangeNumber(), ackList); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Add an update to the list of update received from a replicationServer and |
| | | * waiting for acks. |
| | | * |
| | | * @param update The update that must be added to the list. |
| | | * @param ChangelogServerId The identifier of the replicationServer that sent |
| | | * the update. |
| | | * @param replicationServerDomain The ReplicationServerDomain from which the |
| | | * change was processed and to which the ack |
| | | * must later be sent. |
| | | * @param nbWaitedAck The number of ack that must be received before |
| | | * the update is fully acked. |
| | | */ |
| | | public static void addWaitingAck( |
| | | UpdateMsg update, |
| | | short ChangelogServerId, ReplicationServerDomain replicationServerDomain, |
| | | int nbWaitedAck) |
| | | { |
| | | ReplServerAckMessageList ackList = |
| | | new ReplServerAckMessageList(update.getChangeNumber(), |
| | | nbWaitedAck, |
| | | ChangelogServerId, |
| | | replicationServerDomain); |
| | | synchronized (changelogsWaitingAcks) |
| | | { |
| | | changelogsWaitingAcks.put(update.getChangeNumber(), ackList); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the size of the list of update waiting for acks. |
| | | * |
| | | * @return the size of the list of update waiting for acks. |
| | | */ |
| | | public int getWaitingAckSize() |
| | | { |
| | | synchronized (waitingAcks) |
| | | { |
| | | return waitingAcks.size(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Increment the count of Acks received from this server. |
| | | */ |
| | | public void incrementInAckCount() |
| | | { |
| | | inAckCount++; |
| | | } |
| | | |
| | | /** |
| | |
| | | .valueOf(serverId))); |
| | | attributes.add(Attributes.create("base-dn", baseDn.toString())); |
| | | |
| | | if (serverIsLDAPserver) |
| | | try |
| | | { |
| | | MonitorData md; |
| | | try |
| | | { |
| | | md = replicationServerDomain.getMonitorData(); |
| | | md = replicationServerDomain.getMonitorData(); |
| | | |
| | | if (serverIsLDAPserver) |
| | | { |
| | | // Oldest missing update |
| | | Long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId); |
| | | if ((approxFirstMissingDate != null) && (approxFirstMissingDate > 0)) |
| | |
| | | "approx-older-change-not-synchronized", date.toString())); |
| | | attributes.add(Attributes.create( |
| | | "approx-older-change-not-synchronized-millis", String |
| | | .valueOf(approxFirstMissingDate))); |
| | | .valueOf(approxFirstMissingDate))); |
| | | } |
| | | |
| | | // Missing changes |
| | |
| | | attributes.add(Attributes.create("approximate-delay", String |
| | | .valueOf(delay))); |
| | | } |
| | | catch (Exception e) |
| | | else |
| | | { |
| | | // TODO: improve the log |
| | | // We failed retrieving the remote monitor data. |
| | | attributes.add(Attributes.create("error", |
| | | stackTraceToSingleLineString(e))); |
| | | // Missing changes |
| | | long missingChanges = md.getMissingChangesRS(serverId); |
| | | attributes.add(Attributes.create("missing-changes", String |
| | | .valueOf(missingChanges))); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | // TODO: improve the log |
| | | // We failed retrieving the remote monitor data. |
| | | attributes.add(Attributes.create("error", |
| | | stackTraceToSingleLineString(e))); |
| | | } |
| | | |
| | | attributes.add( |
| | | Attributes.create("queue-size", String.valueOf(msgQueue.count()))); |
| | |
| | | attributes.add(Attributes.create("update-received", String |
| | | .valueOf(getInCount()))); |
| | | |
| | | // Deprecated as long as assured is not exposed |
| | | attributes.add(Attributes.create("update-waiting-acks", String |
| | | .valueOf(getWaitingAckSize()))); |
| | | attributes.add(Attributes.create("ack-sent", String |
| | | .valueOf(getOutAckCount()))); |
| | | attributes.add(Attributes.create("ack-received", String |
| | | .valueOf(getInAckCount()))); |
| | | |
| | | // Window stats |
| | | attributes.add(Attributes.create("max-send-window", String |
| | | .valueOf(sendWindowSize))); |
| | |
| | | /* |
| | | * Stop the remote LSHandler |
| | | */ |
| | | for (LightweightServerHandler lsh : directoryServers.values()) |
| | | synchronized (directoryServers) |
| | | { |
| | | lsh.stopHandler(); |
| | | for (LightweightServerHandler lsh : directoryServers.values()) |
| | | { |
| | | lsh.stopHandler(); |
| | | } |
| | | directoryServers.clear(); |
| | | } |
| | | directoryServers.clear(); |
| | | |
| | | /* |
| | | * Stop the heartbeat thread. |
| | |
| | | { |
| | | WindowMsg msg = new WindowMsg(rcvWindowSizeHalf); |
| | | session.publish(msg); |
| | | outAckCount++; |
| | | rcvWindow += rcvWindowSizeHalf; |
| | | } |
| | | } |
| | |
| | | */ |
| | | List<DSInfo> dsInfos = topoMsg.getDsList(); |
| | | |
| | | // Removes the existing structures |
| | | for (LightweightServerHandler lsh : directoryServers.values()) |
| | | synchronized (directoryServers) |
| | | { |
| | | lsh.stopHandler(); |
| | | } |
| | | directoryServers.clear(); |
| | | // 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); |
| | | // 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); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | public boolean hasRemoteLDAPServers() |
| | | { |
| | | return !directoryServers.isEmpty(); |
| | | synchronized (directoryServers) |
| | | { |
| | | return !directoryServers.isEmpty(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | // TODO also log an error message. |
| | | WindowMsg msg = new WindowMsg(rcvWindow); |
| | | session.publish(msg); |
| | | outAckCount++; |
| | | } else |
| | | { |
| | | // Both the LDAP server and the replication server believes that the |
| | |
| | | */ |
| | | public Set<Short> getConnectedDirectoryServerIds() |
| | | { |
| | | return directoryServers.keySet(); |
| | | } |
| | | |
| | | /** |
| | | * Get the map of connected DSs |
| | | * (to the RS represented by this server handler). |
| | | * @return The map of connected DSs |
| | | */ |
| | | public Map<Short, LightweightServerHandler> getConnectedDSs() |
| | | { |
| | | return directoryServers; |
| | | synchronized (directoryServers) |
| | | { |
| | | return directoryServers.keySet(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | 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) |
| | | { |
| | | for (LightweightServerHandler ls : directoryServers.values()) |
| | | { |
| | | dsInfos.add(ls.toDSInfo()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Gets the group id of the server represented by this object. |
| | | * @return The group id of the server represented by this object. |
| | | */ |
| | | public byte getGroupId() |
| | | { |
| | | return groupId; |
| | | } |
| | | } |
| | |
| | | { |
| | | AckMsg ack = (AckMsg) msg; |
| | | handler.checkWindow(); |
| | | replicationServerDomain.ack(ack, serverId); |
| | | replicationServerDomain.processAck(ack, handler); |
| | | } else if (msg instanceof UpdateMsg) |
| | | { |
| | | boolean filtered = false; |
| | |
| | | logError(ERR_IGNORING_UPDATE_FROM_DS_BADGENID.get( |
| | | Short.toString(replicationServerDomain. |
| | | getReplicationServer().getServerId()), |
| | | replicationServerDomain.getBaseDn().toNormalizedString(), |
| | | replicationServerDomain.getBaseDn(), |
| | | ((UpdateMsg) msg).getChangeNumber().toString(), |
| | | Short.toString(handler.getServerId()), |
| | | Long.toString(referenceGenerationId), |
| | |
| | | logError(ERR_IGNORING_UPDATE_FROM_DS_FULLUP.get( |
| | | Short.toString(replicationServerDomain. |
| | | getReplicationServer().getServerId()), |
| | | replicationServerDomain.getBaseDn().toNormalizedString(), |
| | | replicationServerDomain.getBaseDn(), |
| | | ((UpdateMsg) msg).getChangeNumber().toString(), |
| | | Short.toString(handler.getServerId()))); |
| | | filtered = true; |
| | |
| | | logError(ERR_IGNORING_UPDATE_FROM_RS.get( |
| | | Short.toString(replicationServerDomain.getReplicationServer(). |
| | | getServerId()), |
| | | replicationServerDomain.getBaseDn().toNormalizedString(), |
| | | replicationServerDomain.getBaseDn(), |
| | | ((UpdateMsg) msg).getChangeNumber().toString(), |
| | | Short.toString(handler.getServerId()), |
| | | Long.toString(referenceGenerationId), |
| | |
| | | logError(ERR_IGNORING_UPDATE_TO_DS_BADGENID.get( |
| | | Short.toString(replicationServerDomain.getReplicationServer(). |
| | | getServerId()), |
| | | replicationServerDomain.getBaseDn().toNormalizedString(), |
| | | replicationServerDomain.getBaseDn(), |
| | | update.getChangeNumber().toString(), |
| | | Short.toString(handler.getServerId()), |
| | | Long.toString(handler.getGenerationId()), |
| | |
| | | logError(ERR_IGNORING_UPDATE_TO_DS_FULLUP.get( |
| | | Short.toString(replicationServerDomain.getReplicationServer(). |
| | | getServerId()), |
| | | replicationServerDomain.getBaseDn().toNormalizedString(), |
| | | replicationServerDomain.getBaseDn(), |
| | | update.getChangeNumber().toString(), |
| | | Short.toString(handler.getServerId()))); |
| | | continue; |
| | |
| | | logError(ERR_IGNORING_UPDATE_TO_RS.get( |
| | | Short.toString(replicationServerDomain.getReplicationServer(). |
| | | getServerId()), |
| | | replicationServerDomain.getBaseDn().toNormalizedString(), |
| | | replicationServerDomain.getBaseDn(), |
| | | update.getChangeNumber().toString(), |
| | | Short.toString(handler.getServerId()), |
| | | Long.toString(handler.getGenerationId()), |
| | |
| | | int degradedStatusThreshold) |
| | | { |
| | | super("Replication Server Status Analyzer for " + |
| | | replicationServerDomain.getBaseDn().toNormalizedString() + " in RS " + |
| | | replicationServerDomain.getBaseDn() + " in RS " + |
| | | replicationServerDomain.getReplicationServer().getServerId()); |
| | | |
| | | this.replicationServerDomain = replicationServerDomain; |
| File was renamed from opends/src/server/org/opends/server/tasks/InitializeTargetTask.java |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.tasks; |
| | | package org.opends.server.replication.service; |
| | | import org.opends.server.tasks.TaskUtils; |
| | | |
| | | import org.opends.messages.TaskMessages; |
| | | import org.opends.server.types.ResultCode; |
| | | |
| | | import org.opends.messages.MessageBuilder; |
| | | |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | |
| | | |
| | | import org.opends.server.backends.task.Task; |
| | | import org.opends.server.backends.task.TaskState; |
| | | import org.opends.messages.TaskMessages; |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.Entry; |
| | | |
| | | |
| | | import org.opends.server.types.ResultCode; |
| | | |
| | | /** |
| | | * This class provides an implementation of a Directory Server task that can |
| | | * be used to import data from an LDIF file into a backend. |
| | |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | // Config properties |
| | | boolean append = false; |
| | | boolean isCompressed = false; |
| | | boolean isEncrypted = false; |
| | | boolean skipSchemaValidation = false; |
| | | String domainString = null; |
| | | ReplicationDomain domain = null; |
| | | short target; |
| | | long total; |
| | | long left; |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public Message getDisplayName() { |
| | | return TaskMessages.INFO_TASK_INITIALIZE_TARGET_NAME.get(); |
| | | } |
| | | private String domainString = null; |
| | | private ReplicationDomain domain = null; |
| | | private short target; |
| | | private long total; |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | |
| | | attrList = taskEntry.getAttribute(typeDomainBase); |
| | | domainString = TaskUtils.getSingleValueString(attrList); |
| | | |
| | | DN domainDN = DN.nullDN(); |
| | | try |
| | | { |
| | | domainDN = DN.decode(domainString); |
| | | domain = ReplicationDomain.retrievesReplicationDomain(domainString); |
| | | } |
| | | catch(Exception e) |
| | | catch(DirectoryException e) |
| | | { |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(TaskMessages.ERR_TASK_INITIALIZE_INVALID_DN.get()); |
| | | mb.append(e.getLocalizedMessage()); |
| | | throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, |
| | | mb.toMessage()); |
| | | mb.append(e.getMessage()); |
| | | throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, e); |
| | | } |
| | | domain=ReplicationDomain.retrievesReplicationDomain(domainDN); |
| | | |
| | | attrList = taskEntry.getAttribute(typeScope); |
| | | String targetString = TaskUtils.getSingleValueString(attrList); |
| | |
| | | */ |
| | | public void setLeft(long left) throws DirectoryException |
| | | { |
| | | this.left = left; |
| | | replaceAttributeValue(ATTR_TASK_INITIALIZE_LEFT, String.valueOf(left)); |
| | | replaceAttributeValue(ATTR_TASK_INITIALIZE_DONE,String.valueOf(total-left)); |
| | | } |
| File was renamed from opends/src/server/org/opends/server/tasks/InitializeTask.java |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.tasks; |
| | | import org.opends.messages.Message; |
| | | package org.opends.server.replication.service; |
| | | import org.opends.server.tasks.TaskUtils; |
| | | |
| | | import org.opends.server.types.ResultCode; |
| | | |
| | | import org.opends.messages.MessageBuilder; |
| | | |
| | | |
| | | import org.opends.messages.Message; |
| | | |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | | import static org.opends.server.core.DirectoryServer.getAttributeType; |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | |
| | | import org.opends.server.backends.task.Task; |
| | | import org.opends.server.backends.task.TaskState; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.ResultCode; |
| | | |
| | | /** |
| | | * This class provides an implementation of a Directory Server task that can |
| | |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | boolean isCompressed = false; |
| | | boolean isEncrypted = false; |
| | | boolean skipSchemaValidation = false; |
| | | String domainString = null; |
| | | short source; |
| | | ReplicationDomain domain = null; |
| | | TaskState initState; |
| | | private String domainString = null; |
| | | private short source; |
| | | private ReplicationDomain domain = null; |
| | | private TaskState initState; |
| | | |
| | | // The total number of entries expected to be processed when this import |
| | | // will end successfully |
| | |
| | | List<Attribute> attrList; |
| | | attrList = taskEntry.getAttribute(typeDomainBase); |
| | | domainString = TaskUtils.getSingleValueString(attrList); |
| | | DN domainDN = DN.nullDN(); |
| | | |
| | | try |
| | | { |
| | | domainDN = DN.decode(domainString); |
| | | domain = ReplicationDomain.retrievesReplicationDomain(domainString); |
| | | } |
| | | catch(Exception e) |
| | | catch(DirectoryException e) |
| | | { |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(TaskMessages.ERR_TASK_INITIALIZE_INVALID_DN.get()); |
| | | mb.append(e.getMessage()); |
| | | throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, |
| | | mb.toMessage()); |
| | | throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, e); |
| | | } |
| | | |
| | | domain = ReplicationDomain.retrievesReplicationDomain(domainDN); |
| | | |
| | | attrList = taskEntry.getAttribute(typeSourceScope); |
| | | String sourceString = TaskUtils.getSingleValueString(attrList); |
| | | source = domain.decodeSource(sourceString); |
| | |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("InitializeTask is starting domain: %s source:%d", |
| | | domain.getBaseDN(), source); |
| | | domain.getServiceID(), source); |
| | | } |
| | | initState = getTaskState(); |
| | | try |
| File was renamed from opends/src/server/org/opends/server/replication/plugin/ListenerThread.java |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.TimeUnit; |
| | | package org.opends.server.replication.service; |
| | | import org.opends.messages.Message; |
| | | |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | |
| | | private ReplicationDomain repDomain; |
| | | private boolean shutdown = false; |
| | | private boolean done = false; |
| | | private LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue; |
| | | |
| | | |
| | | /** |
| | | * Constructor for the ListenerThread. |
| | | * |
| | | * @param repDomain the replication domain that created this thread |
| | | * @param updateToReplayQueue The update messages queue we must |
| | | * store messages in |
| | | */ |
| | | public ListenerThread(ReplicationDomain repDomain, |
| | | LinkedBlockingQueue<UpdateToReplay> updateToReplayQueue) |
| | | public ListenerThread(ReplicationDomain repDomain) |
| | | { |
| | | super("Replication Listener for server id " + repDomain.getServerId() + |
| | | " and domain " + repDomain.getBaseDN()); |
| | | " and domain " + repDomain.getServiceID()); |
| | | this.repDomain = repDomain; |
| | | this.updateToReplayQueue = updateToReplayQueue; |
| | | } |
| | | |
| | | /** |
| | |
| | | // queue |
| | | while ((!shutdown) && ((updateMsg = repDomain.receive()) != null)) |
| | | { |
| | | // Put update message into the queue (block until some place in the |
| | | // queue is available) |
| | | UpdateToReplay updateToReplay = |
| | | new UpdateToReplay(updateMsg, repDomain); |
| | | boolean queued = false; |
| | | while (!queued && !shutdown) |
| | | { |
| | | // Use timedout method (offer) instead of put for being able to |
| | | // shutdown the thread |
| | | queued = updateToReplayQueue.offer(updateToReplay, |
| | | 1L, TimeUnit.SECONDS); |
| | | } |
| | | if (!queued) |
| | | { |
| | | // Shutdown requested but could not push message: ensure this one is |
| | | // not lost and put it in the queue before dying |
| | | updateToReplayQueue.offer(updateToReplay); |
| | | } |
| | | if (repDomain.processUpdate(updateMsg) == true) |
| | | repDomain.processUpdateDoneSynchronous(updateMsg); |
| | | } |
| | | if (updateMsg == null) |
| | | shutdown = true; |
| | |
| | | } |
| | | } |
| | | |
| | | // Stop the HeartBeat thread |
| | | repDomain.getBroker().stopHeartBeat(); |
| | | |
| | | done = true; |
| | | |
| | | if (debugEnabled()) |
| | |
| | | if (n >= FACTOR) |
| | | { |
| | | TRACER.debugInfo("Interrupting listener thread for dn " + |
| | | repDomain.getBaseDN() + " in DS " + repDomain.getServerId()); |
| | | repDomain.getServiceID() + " in DS " + repDomain.getServerId()); |
| | | this.interrupt(); |
| | | } |
| | | } |
| File was renamed from opends/src/server/org/opends/server/replication/plugin/ReplLDIFInputStream.java |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | package org.opends.server.replication.service; |
| | | |
| | | |
| | | |
| | |
| | | * This class creates an input stream that can be used to read entries generated |
| | | * by SynchroLDIF as if they were being read from another source like a file. |
| | | */ |
| | | public class ReplLDIFInputStream |
| | | public class ReplInputStream |
| | | extends InputStream |
| | | { |
| | | // Indicates whether this input stream has been closed. |
| | |
| | | |
| | | /** |
| | | * Creates a new ReplLDIFInputStream that will import entries |
| | | * for a synchronzation domain. |
| | | * for a synchronization domain. |
| | | * |
| | | * @param domain The replication domain |
| | | */ |
| | | public ReplLDIFInputStream(ReplicationDomain domain) |
| | | public ReplInputStream(ReplicationDomain domain) |
| | | { |
| | | this.domain = domain; |
| | | closed = false; |
| | | closed = false; |
| | | } |
| | | |
| | | /** |
| New file |
| | |
| | | /* |
| | | * 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-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | | |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | |
| | | /** |
| | | * This class creates an output stream that can be used to export entries |
| | | * to a synchronization domain. |
| | | */ |
| | | public class ReplOutputStream |
| | | extends OutputStream |
| | | { |
| | | // The synchronization domain on which the export is done |
| | | ReplicationDomain domain; |
| | | |
| | | // The current number of entries exported |
| | | private long numExportedEntries; |
| | | String entryBuffer = ""; |
| | | |
| | | /** |
| | | * Creates a new ReplLDIFOutputStream related to a replication |
| | | * domain. |
| | | * |
| | | * @param domain The replication domain |
| | | */ |
| | | public ReplOutputStream(ReplicationDomain domain) |
| | | { |
| | | this.domain = domain; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void write(int i) throws IOException |
| | | { |
| | | throw new IOException("Invalid call"); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void write(byte b[], int off, int len) throws IOException |
| | | { |
| | | domain.exportLDIFEntry(b, off, len); |
| | | } |
| | | |
| | | /** |
| | | * Return the number of exported entries. |
| | | * @return the numExportedEntries |
| | | */ |
| | | public long getNumExportedEntries() { |
| | | return numExportedEntries; |
| | | } |
| | | } |
| File was renamed from opends/src/server/org/opends/server/replication/plugin/ReplicationBroker.java |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | 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.util.StaticUtils.stackTraceToSingleLineString; |
| | |
| | | import java.util.Collection; |
| | | import java.util.HashMap; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.TreeSet; |
| | | import java.util.concurrent.Semaphore; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.protocols.asn1.ASN1OctetString; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchListener; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | 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.types.DN; |
| | | import org.opends.server.types.DereferencePolicy; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchResultReference; |
| | | import org.opends.server.types.SearchScope; |
| | | |
| | | /** |
| | | * The broker for Multi-master Replication. |
| | | */ |
| | | public class ReplicationBroker implements InternalSearchListener |
| | | public class ReplicationBroker |
| | | { |
| | | |
| | | /** |
| | |
| | | private Collection<String> servers; |
| | | private boolean connected = false; |
| | | private String replicationServer = "Not connected"; |
| | | private TreeSet<FakeOperation> replayOperations; |
| | | private ProtocolSession session = null; |
| | | private final ServerState state; |
| | | private final DN baseDn; |
| | | private final String baseDn; |
| | | private final short serverId; |
| | | private int maxSendDelay; |
| | | private int maxReceiveDelay; |
| | | private int maxSendQueue; |
| | | private int maxReceiveQueue; |
| | | private Semaphore sendWindow; |
| | | private int maxSendWindow; |
| | | private int rcvWindow; |
| | | private int halfRcvWindow; |
| | | private int maxRcvWindow; |
| | | private int rcvWindow = 100; |
| | | private int halfRcvWindow = rcvWindow/2; |
| | | private int maxRcvWindow = rcvWindow; |
| | | private int timeout = 0; |
| | | private short protocolVersion; |
| | | private long generationId = -1; |
| | | private ReplSessionSecurity replSessionSecurity; |
| | | // My group id |
| | | private byte groupId = (byte) -1; |
| | |
| | | // The server URL of the RS we are connected to |
| | | private String rsServerUrl = null; |
| | | // Our replication domain |
| | | private ReplicationDomain replicationDomain = null; |
| | | private ReplicationDomain domain = null; |
| | | |
| | | // Trick for avoiding a inner class for many parameters return for |
| | | // performPhaseOneHandshake method. |
| | |
| | | // Same group id poller thread |
| | | private SameGroupIdPoller sameGroupIdPoller = null; |
| | | |
| | | /* |
| | | * Properties for the last topology info received from the network. |
| | | */ |
| | | // Info for other DSs. |
| | | // Warning: does not contain info for us (for our server id) |
| | | private List<DSInfo> dsList = new ArrayList<DSInfo>(); |
| | | // Info for other RSs. |
| | | private List<RSInfo> rsList = new ArrayList<RSInfo>(); |
| | | |
| | | private long generationID; |
| | | |
| | | /** |
| | | * Creates a new ReplicationServer Broker for a particular ReplicationDomain. |
| | | * |
| | |
| | | * when negotiating the session with the replicationServer. |
| | | * @param serverId The server ID that should be used by this broker |
| | | * when negotiating the session with the replicationServer. |
| | | * @param maxReceiveQueue The maximum size of the receive queue to use on |
| | | * the replicationServer. |
| | | * @param maxReceiveDelay The maximum replication delay to use on the |
| | | * replicationServer. |
| | | * @param maxSendQueue The maximum size of the send queue to use on |
| | | * the replicationServer. |
| | | * @param maxSendDelay The maximum send delay to use on the replicationServer. |
| | | * @param window The size of the send and receive window to use. |
| | | * @param heartbeatInterval The interval between heartbeats requested of the |
| | | * replicationServer, or zero if no heartbeats are requested. |
| | |
| | | * @param groupId The group id of our domain. |
| | | */ |
| | | public ReplicationBroker(ReplicationDomain replicationDomain, |
| | | ServerState state, DN baseDn, short serverId, int maxReceiveQueue, |
| | | int maxReceiveDelay, int maxSendQueue, int maxSendDelay, int window, |
| | | long heartbeatInterval, long generationId, |
| | | ServerState state, String baseDn, short serverId, int window, |
| | | long generationId, long heartbeatInterval, |
| | | ReplSessionSecurity replSessionSecurity, byte groupId) |
| | | { |
| | | this.replicationDomain = replicationDomain; |
| | | this.domain = replicationDomain; |
| | | this.baseDn = baseDn; |
| | | this.serverId = serverId; |
| | | this.maxReceiveDelay = maxReceiveDelay; |
| | | this.maxSendDelay = maxSendDelay; |
| | | this.maxReceiveQueue = maxReceiveQueue; |
| | | this.maxSendQueue = maxSendQueue; |
| | | this.state = state; |
| | | replayOperations = |
| | | new TreeSet<FakeOperation>(new FakeOperationComparator()); |
| | | this.rcvWindow = window; |
| | | this.maxRcvWindow = window; |
| | | this.halfRcvWindow = window / 2; |
| | | this.heartbeatInterval = heartbeatInterval; |
| | | this.protocolVersion = ProtocolVersion.getCurrentVersion(); |
| | | this.generationId = generationId; |
| | | this.replSessionSecurity = replSessionSecurity; |
| | | this.groupId = groupId; |
| | | this.generationID = generationId; |
| | | this.heartbeatInterval = heartbeatInterval; |
| | | this.maxRcvWindow = window; |
| | | this.maxRcvWindow = window; |
| | | this.halfRcvWindow = window /2; |
| | | } |
| | | |
| | | /** |
| | | * Start the ReplicationBroker. |
| | | */ |
| | | public void start() |
| | | { |
| | | shutdown = false; |
| | | this.rcvWindow = this.maxRcvWindow; |
| | | this.connect(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | shutdown = false; |
| | | this.servers = servers; |
| | | |
| | | if (servers.size() < 1) |
| | | { |
| | | Message message = NOTE_NEED_MORE_THAN_ONE_CHANGELOG_SERVER.get(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Gets the server id. |
| | | * @return The server id |
| | | */ |
| | | private long getGenerationID() |
| | | { |
| | | if (domain != null) |
| | | return domain.getGenerationID(); |
| | | else |
| | | return generationID; |
| | | } |
| | | |
| | | /** |
| | | * Gets the server url of the RS we are connected to. |
| | | * @return The server url of the RS we are connected to |
| | | */ |
| | |
| | | |
| | | // May have created a broker with null replication domain for |
| | | // unit test purpose. |
| | | if (replicationDomain != null) |
| | | if (domain != null) |
| | | { |
| | | // If a first connect or a connection failure occur, we go through here. |
| | | // force status machine to NOT_CONNECTED_STATUS so that monitoring can |
| | | // see that we are not connected. |
| | | replicationDomain.toNotConnectedStatus(); |
| | | domain.toNotConnectedStatus(); |
| | | } |
| | | |
| | | // Stop any existing poller and heartbeat monitor from a previous session. |
| | |
| | | ServerStatus initStatus = |
| | | computeInitialServerStatus(replServerStartMsg.getGenerationId(), |
| | | bestServerInfo.getServerState(), |
| | | replServerStartMsg.getDegradedStatusThreshold(), generationId); |
| | | replServerStartMsg.getDegradedStatusThreshold(), |
| | | this.getGenerationID()); |
| | | |
| | | // Perfom session start (handshake phase 2) |
| | | TopologyMsg topologyMsg = performPhaseTwoHandshake(bestServer, |
| | |
| | | if ((tmpRsGroupId == groupId) || |
| | | ((tmpRsGroupId != groupId) && !someServersWithSameGroupId)) |
| | | { |
| | | /* |
| | | * We must not publish changes to a replicationServer that has |
| | | * not seen all our previous changes because this could cause |
| | | * some other ldap servers to miss those changes. |
| | | * Check that the ReplicationServer has seen all our previous |
| | | * changes. |
| | | */ |
| | | ChangeNumber replServerMaxChangeNumber = |
| | | replServerStartMsg.getServerState(). |
| | | getMaxChangeNumber(serverId); |
| | | |
| | | if (replServerMaxChangeNumber == null) |
| | | { |
| | | replServerMaxChangeNumber = new ChangeNumber(0, 0, serverId); |
| | | } |
| | | ChangeNumber ourMaxChangeNumber = |
| | | state.getMaxChangeNumber(serverId); |
| | | |
| | | if ((ourMaxChangeNumber != null) && |
| | | (!ourMaxChangeNumber.olderOrEqual(replServerMaxChangeNumber))) |
| | | { |
| | | |
| | | // Replication server is missing some of our changes: let's |
| | | // send them to him. |
| | | |
| | | Message message = DEBUG_GOING_TO_SEARCH_FOR_CHANGES.get(); |
| | | logError(message); |
| | | |
| | | /* |
| | | * Get all the changes that have not been seen by this |
| | | * replication server and populate the replayOperations |
| | | * list. |
| | | */ |
| | | InternalSearchOperation op = searchForChangedEntries( |
| | | baseDn, replServerMaxChangeNumber, this); |
| | | if (op.getResultCode() != ResultCode.SUCCESS) |
| | | { |
| | | /* |
| | | * An error happened trying to search for the updates |
| | | * This server will start acepting again new updates but |
| | | * some inconsistencies will stay between servers. |
| | | * Log an error for the repair tool |
| | | * that will need to resynchronize the servers. |
| | | */ |
| | | message = ERR_CANNOT_RECOVER_CHANGES.get( |
| | | baseDn.toNormalizedString()); |
| | | logError(message); |
| | | } else |
| | | { |
| | | for (FakeOperation replayOp : replayOperations) |
| | | { |
| | | ChangeNumber cn = replayOp.getChangeNumber(); |
| | | /* |
| | | * Because the entry returned by the search operation |
| | | * can contain old historical information, it is |
| | | * possible that some of the FakeOperation are |
| | | * actually older than the |
| | | * Only send the Operation if it was newer than |
| | | * the last ChangeNumber known by the Replication Server. |
| | | */ |
| | | if (cn.newer(replServerMaxChangeNumber)) |
| | | { |
| | | message = |
| | | DEBUG_SENDING_CHANGE.get( |
| | | replayOp.getChangeNumber().toString()); |
| | | logError(message); |
| | | session.publish(replayOp.generateMessage()); |
| | | } |
| | | } |
| | | message = DEBUG_CHANGES_SENT.get(); |
| | | logError(message); |
| | | } |
| | | replayOperations.clear(); |
| | | } |
| | | |
| | | replicationServer = tmpReadableServerName; |
| | | maxSendWindow = replServerStartMsg.getWindowSize(); |
| | | rsGroupId = replServerStartMsg.getGroupId(); |
| | |
| | | |
| | | // May have created a broker with null replication domain for |
| | | // unit test purpose. |
| | | if (replicationDomain != null) |
| | | if (domain != null) |
| | | { |
| | | replicationDomain.setInitialStatus(initStatus); |
| | | replicationDomain.receiveTopo(topologyMsg); |
| | | domain.sessionInitiated( |
| | | initStatus, replServerStartMsg.getServerState(), |
| | | session); |
| | | } |
| | | receiveTopo(topologyMsg); |
| | | connected = true; |
| | | if (getRsGroupId() != groupId) |
| | | { |
| | |
| | | // Do not log connection error |
| | | newServerWithSameGroupId = true; |
| | | } |
| | | } catch (IOException e) |
| | | { |
| | | Message message = ERR_PUBLISHING_FAKE_OPS.get( |
| | | baseDn.toNormalizedString(), bestServer, |
| | | e.getLocalizedMessage() + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | } catch (Exception e) |
| | | { |
| | | Message message = ERR_COMPUTING_FAKE_OPS.get( |
| | | baseDn.toNormalizedString(), bestServer, |
| | | baseDn, bestServer, |
| | | e.getLocalizedMessage() + stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | } finally |
| | |
| | | this.sendWindow = new Semaphore(maxSendWindow); |
| | | connectPhaseLock.notify(); |
| | | |
| | | if ((replServerStartMsg.getGenerationId() == this.generationId) || |
| | | if ((replServerStartMsg.getGenerationId() == this.getGenerationID()) || |
| | | (replServerStartMsg.getGenerationId() == -1)) |
| | | { |
| | | Message message = |
| | |
| | | Short.toString(rsServerId), |
| | | replicationServer, |
| | | Short.toString(serverId), |
| | | Long.toString(this.generationId)); |
| | | Long.toString(this.getGenerationID())); |
| | | logError(message); |
| | | } else |
| | | { |
| | |
| | | NOTE_NOW_FOUND_BAD_GENERATION_CHANGELOG.get( |
| | | baseDn.toString(), |
| | | replicationServer, |
| | | Long.toString(this.generationId), |
| | | Long.toString(this.getGenerationID()), |
| | | Long.toString(replServerStartMsg.getGenerationId())); |
| | | logError(message); |
| | | } |
| | |
| | | |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("RB for dn " + baseDn.toNormalizedString() + |
| | | TRACER.debugInfo("RB for dn " + baseDn + |
| | | " and with server id " + Short.toString(serverId) + " computed " + |
| | | Integer.toString(nChanges) + " changes late."); |
| | | } |
| | |
| | | /* |
| | | * Send our ServerStartMsg. |
| | | */ |
| | | ServerStartMsg serverStartMsg = new ServerStartMsg(serverId, baseDn, |
| | | maxReceiveDelay, maxReceiveQueue, maxSendDelay, maxSendQueue, |
| | | halfRcvWindow * 2, heartbeatInterval, state, |
| | | ProtocolVersion.getCurrentVersion(), generationId, isSslEncryption, |
| | | ServerStartMsg serverStartMsg = new ServerStartMsg(serverId, |
| | | baseDn, 0, 0, 0, 0, |
| | | maxRcvWindow, heartbeatInterval, state, |
| | | ProtocolVersion.getCurrentVersion(), this.getGenerationID(), |
| | | isSslEncryption, |
| | | groupId); |
| | | localSession.publish(serverStartMsg); |
| | | |
| | |
| | | } |
| | | |
| | | // Sanity check |
| | | DN repDn = replServerStartMsg.getBaseDn(); |
| | | String repDn = replServerStartMsg.getBaseDn(); |
| | | if (!(this.baseDn.equals(repDn))) |
| | | { |
| | | Message message = ERR_DS_DN_DOES_NOT_MATCH.get(repDn.toString(), |
| | | this.baseDn.toString()); |
| | | this.baseDn); |
| | | logError(message); |
| | | error = true; |
| | | } |
| | |
| | | if ( (e instanceof SocketTimeoutException) && debugEnabled() ) |
| | | { |
| | | TRACER.debugInfo("Timeout trying to connect to RS " + server + |
| | | " for dn: " + baseDn.toNormalizedString()); |
| | | " for dn: " + baseDn); |
| | | } |
| | | Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("1", |
| | | baseDn.toNormalizedString(), server, e.getLocalizedMessage() + |
| | | baseDn, server, e.getLocalizedMessage() + |
| | | stackTraceToSingleLineString(e)); |
| | | if (keepConnection) // Log error message only for final connection |
| | | { |
| | |
| | | StartSessionMsg startSessionMsg = null; |
| | | // May have created a broker with null replication domain for |
| | | // unit test purpose. |
| | | if (replicationDomain != null) |
| | | if (domain != null) |
| | | { |
| | | startSessionMsg = new StartSessionMsg(initStatus, |
| | | replicationDomain.getRefUrls(), |
| | | replicationDomain.isAssured(), |
| | | replicationDomain.getAssuredMode(), |
| | | replicationDomain.getAssuredSdLevel()); |
| | | startSessionMsg = |
| | | new StartSessionMsg( |
| | | initStatus, |
| | | domain.getRefUrls(), |
| | | domain.isAssured(), |
| | | domain.getAssuredMode(), |
| | | domain.getAssuredSdLevel()); |
| | | } else |
| | | { |
| | | startSessionMsg = |
| | |
| | | } catch (Exception e) |
| | | { |
| | | Message message = ERR_EXCEPTION_STARTING_SESSION_PHASE.get("2", |
| | | baseDn.toNormalizedString(), server, e.getLocalizedMessage() + |
| | | baseDn, server, e.getLocalizedMessage() + |
| | | stackTraceToSingleLineString(e)); |
| | | logError(message); |
| | | |
| | |
| | | * @return The computed best replication server. |
| | | */ |
| | | public static String computeBestReplicationServer(ServerState myState, |
| | | HashMap<String, ServerInfo> rsInfos, short serverId, DN baseDn, |
| | | HashMap<String, ServerInfo> rsInfos, short serverId, String baseDn, |
| | | byte groupId) |
| | | { |
| | | /* |
| | |
| | | * @return The computed best replication server. |
| | | */ |
| | | private static String searchForBestReplicationServer(ServerState myState, |
| | | HashMap<String, ServerInfo> rsInfos, short serverId, DN baseDn) |
| | | HashMap<String, ServerInfo> rsInfos, short serverId, String baseDn) |
| | | { |
| | | /* |
| | | * Find replication servers who are up to date (or more up to date than us, |
| | |
| | | */ |
| | | |
| | | Message message = NOTE_FOUND_CHANGELOGS_WITH_MY_CHANGES.get( |
| | | upToDateServers.size(), |
| | | baseDn.toNormalizedString(), |
| | | Short.toString(serverId)); |
| | | upToDateServers.size(), baseDn, Short.toString(serverId)); |
| | | logError(message); |
| | | |
| | | /* |
| | |
| | | */ |
| | | // lateOnes cannot be empty |
| | | Message message = NOTE_COULD_NOT_FIND_CHANGELOG_WITH_MY_CHANGES.get( |
| | | baseDn.toNormalizedString(), lateOnes.size()); |
| | | baseDn, lateOnes.size()); |
| | | logError(message); |
| | | |
| | | // Min of the shifts |
| | |
| | | } |
| | | |
| | | /** |
| | | * Search for the changes that happened since fromChangeNumber |
| | | * based on the historical attribute. The only changes that will |
| | | * be send will be the one generated on the serverId provided in |
| | | * fromChangeNumber. |
| | | * @param baseDn the base DN |
| | | * @param fromChangeNumber The change number from which we want the changes |
| | | * @param resultListener that will process the entries returned. |
| | | * @return the internal search operation |
| | | * @throws Exception when raised. |
| | | */ |
| | | public static InternalSearchOperation searchForChangedEntries( |
| | | DN baseDn, |
| | | ChangeNumber fromChangeNumber, |
| | | InternalSearchListener resultListener) |
| | | throws Exception |
| | | { |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | Short serverId = fromChangeNumber.getServerId(); |
| | | |
| | | String maxValueForId = "ffffffffffffffff" + |
| | | String.format("%04x", serverId) + "ffffffff"; |
| | | |
| | | LDAPFilter filter = LDAPFilter.decode( |
| | | "(&(" + Historical.HISTORICALATTRIBUTENAME + ">=dummy:" |
| | | + fromChangeNumber + ")(" + Historical.HISTORICALATTRIBUTENAME + |
| | | "<=dummy:" + maxValueForId + "))"); |
| | | |
| | | LinkedHashSet<String> attrs = new LinkedHashSet<String>(1); |
| | | attrs.add(Historical.HISTORICALATTRIBUTENAME); |
| | | attrs.add(Historical.ENTRYUIDNAME); |
| | | attrs.add("*"); |
| | | return conn.processSearch( |
| | | new ASN1OctetString(baseDn.toString()), |
| | | SearchScope.WHOLE_SUBTREE, |
| | | DereferencePolicy.NEVER_DEREF_ALIASES, |
| | | 0, 0, false, filter, |
| | | attrs, |
| | | resultListener); |
| | | } |
| | | |
| | | /** |
| | | * Start the heartbeat monitor thread. |
| | | */ |
| | | private void startHeartBeat() |
| | |
| | | { |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(NOTE_EXCEPTION_RESTARTING_SESSION.get( |
| | | baseDn.toNormalizedString(), e.getLocalizedMessage())); |
| | | baseDn, e.getLocalizedMessage())); |
| | | mb.append(stackTraceToSingleLineString(e)); |
| | | logError(mb.toMessage()); |
| | | } |
| | |
| | | } |
| | | } |
| | | } |
| | | if (!credit) |
| | | if ((!credit) && (currentWindowSemaphore.availablePermits() == 0)) |
| | | { |
| | | // the window is still closed. |
| | | // Send a WindowProbeMsg message to wakeup the receiver in case the |
| | |
| | | if (debugEnabled()) |
| | | { |
| | | debugInfo("ReplicationBroker.publish() " + |
| | | "IO exception raised : " + e.getLocalizedMessage()); |
| | | "Interrupted exception raised : " + e.getLocalizedMessage()); |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | WindowMsg windowMsg = (WindowMsg) msg; |
| | | sendWindow.release(windowMsg.getNumAck()); |
| | | } else |
| | | } |
| | | else if (msg instanceof TopologyMsg) |
| | | { |
| | | TopologyMsg topoMsg = (TopologyMsg)msg; |
| | | receiveTopo(topoMsg); |
| | | } |
| | | else |
| | | { |
| | | return msg; |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Set the value of the generationId for that broker. Normally the |
| | | * generationId is set through the constructor but there are cases |
| | | * where the value of the generationId must be changed while the broker |
| | | * already exist for example after an on-line import. |
| | | * |
| | | * @param generationId The value of the generationId. |
| | | * |
| | | */ |
| | | public void setGenerationId(long generationId) |
| | | { |
| | | this.generationId = generationId; |
| | | } |
| | | |
| | | /** |
| | | * Get the name of the replicationServer to which this broker is currently |
| | | * connected. |
| | | * |
| | |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void handleInternalSearchEntry( |
| | | InternalSearchOperation searchOperation, |
| | | SearchResultEntry searchEntry) |
| | | { |
| | | /* |
| | | * This call back is called at session establishment phase |
| | | * for each entry that has been changed by this server and the changes |
| | | * have not been sent to any Replication Server. |
| | | * The role of this method is to build equivalent operation from |
| | | * the historical information and add them in the replayOperations |
| | | * table. |
| | | */ |
| | | Iterable<FakeOperation> updates = |
| | | Historical.generateFakeOperations(searchEntry); |
| | | for (FakeOperation op : updates) |
| | | { |
| | | replayOperations.add(op); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public void handleInternalSearchReference( |
| | | InternalSearchOperation searchOperation, |
| | | SearchResultReference searchReference) |
| | | { |
| | | // TODO to be implemented |
| | | } |
| | | |
| | | /** |
| | | * Get the maximum receive window size. |
| | | * |
| | | * @return The maximum receive window size. |
| | |
| | | } |
| | | |
| | | /** |
| | | * Change some config parameters. |
| | | * Change some configuration parameters. |
| | | * |
| | | * @param replicationServers The new list of replication servers. |
| | | * @param maxReceiveQueue The max size of receive queue. |
| | | * @param maxReceiveDelay The max receive delay. |
| | | * @param maxSendQueue The max send queue. |
| | | * @param maxSendDelay The max Send Delay. |
| | | * @param replicationServers The new list of replication servers. |
| | | * @param window The max window size. |
| | | * @param heartbeatInterval The heartbeat interval. |
| | | * @param heartbeatInterval The heartBeat interval. |
| | | * |
| | | * @return A boolean indicating if the changes |
| | | * requires to restart the service. |
| | | */ |
| | | public void changeConfig(Collection<String> replicationServers, |
| | | int maxReceiveQueue, int maxReceiveDelay, int maxSendQueue, |
| | | int maxSendDelay, int window, long heartbeatInterval) |
| | | public boolean changeConfig( |
| | | Collection<String> replicationServers, int window, long heartbeatInterval) |
| | | { |
| | | this.servers = replicationServers; |
| | | this.maxRcvWindow = window; |
| | | this.heartbeatInterval = heartbeatInterval; |
| | | this.maxReceiveDelay = maxReceiveDelay; |
| | | this.maxReceiveQueue = maxReceiveQueue; |
| | | this.maxSendDelay = maxSendDelay; |
| | | this.maxSendQueue = maxSendQueue; |
| | | // These parameters needs to be renegociated with the ReplicationServer |
| | | // so if they have changed, that requires restarting the session with |
| | | // the ReplicationServer. |
| | | Boolean needToRestartSession = false; |
| | | |
| | | // For info, a new session with the replicationServer |
| | | // will be recreated in the replication domain |
| | | // to take into account the new configuration. |
| | | // A new session is necessary only when information regarding |
| | | // the connection is modified |
| | | if ((servers == null) || |
| | | (!(replicationServers.size() == servers.size() |
| | | && replicationServers.containsAll(servers))) || |
| | | window != this.maxRcvWindow || |
| | | heartbeatInterval != this.heartbeatInterval) |
| | | { |
| | | needToRestartSession = true; |
| | | } |
| | | |
| | | this.servers = replicationServers; |
| | | this.rcvWindow = window; |
| | | this.maxRcvWindow = window; |
| | | this.halfRcvWindow = window / 2; |
| | | this.heartbeatInterval = heartbeatInterval; |
| | | |
| | | return needToRestartSession; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | try |
| | | { |
| | | |
| | | ChangeStatusMsg csMsg = new ChangeStatusMsg(ServerStatus.INVALID_STATUS, |
| | | newStatus); |
| | | session.publish(csMsg); |
| | | } catch (IOException ex) |
| | | { |
| | | Message message = ERR_EXCEPTION_SENDING_CS.get( |
| | | baseDn.toNormalizedString(), |
| | | baseDn, |
| | | Short.toString(serverId), |
| | | ex.getLocalizedMessage() + stackTraceToSingleLineString(ex)); |
| | | logError(message); |
| | |
| | | { |
| | | this.groupId = groupId; |
| | | } |
| | | |
| | | /** |
| | | * Gets the info for DSs in the topology (except us). |
| | | * @return The info for DSs in the topology (except us) |
| | | */ |
| | | public List<DSInfo> getDsList() |
| | | { |
| | | return dsList; |
| | | } |
| | | |
| | | /** |
| | | * Gets the info for RSs in the topology (except the one we are connected |
| | | * to). |
| | | * @return The info for RSs in the topology (except the one we are connected |
| | | * to) |
| | | */ |
| | | public List<RSInfo> getRsList() |
| | | { |
| | | return rsList; |
| | | } |
| | | |
| | | /** |
| | | * Processes an incoming TopologyMsg. |
| | | * Updates the structures for the local view of the topology. |
| | | * |
| | | * @param topoMsg The topology information received from RS. |
| | | */ |
| | | public void receiveTopo(TopologyMsg topoMsg) |
| | | { |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + baseDn |
| | | + " received topology info update:\n" + topoMsg); |
| | | |
| | | // Store new lists |
| | | synchronized(getDsList()) |
| | | { |
| | | synchronized(getRsList()) |
| | | { |
| | | dsList = topoMsg.getDsList(); |
| | | rsList = topoMsg.getRsList(); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | | 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.replication.common.StatusMachine.*; |
| | | |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | |
| | | import java.io.BufferedOutputStream; |
| | | |
| | | import org.opends.server.types.Attribute; |
| | | |
| | | import org.opends.server.core.DirectoryServer; |
| | | |
| | | |
| | | import java.util.Set; |
| | | |
| | | import org.opends.server.replication.common.DSInfo; |
| | | import org.opends.server.replication.common.RSInfo; |
| | | |
| | | import java.util.HashMap; |
| | | |
| | | import java.util.Map; |
| | | |
| | | import org.opends.server.config.ConfigException; |
| | | import java.util.Collection; |
| | | import org.opends.server.replication.protocol.ReplSessionSecurity; |
| | | import org.opends.server.replication.protocol.ResetGenerationIdMsg; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import java.net.SocketTimeoutException; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.SortedMap; |
| | | import java.util.TreeMap; |
| | | import java.util.concurrent.TimeoutException; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.messages.MessageBuilder; |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.backends.task.Task; |
| | | 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.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.AckMsg; |
| | | import org.opends.server.replication.protocol.ChangeStatusMsg; |
| | | import org.opends.server.replication.protocol.DoneMsg; |
| | | import org.opends.server.replication.protocol.EntryMsg; |
| | | import org.opends.server.replication.protocol.ErrorMsg; |
| | | import org.opends.server.replication.protocol.HeartbeatMsg; |
| | | import org.opends.server.replication.protocol.InitializeRequestMsg; |
| | | import org.opends.server.replication.protocol.InitializeTargetMsg; |
| | | import org.opends.server.replication.protocol.ProtocolSession; |
| | | import org.opends.server.replication.protocol.ProtocolVersion; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.replication.protocol.RoutableMsg; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.ResultCode; |
| | | |
| | | /** |
| | | * This class should be used as a base for Replication implementations. |
| | | * <p> |
| | | * It is intended that developer in need of a replication mechanism |
| | | * subclass this class with their own implementation. |
| | | * <p> |
| | | * The startup phase of the ReplicationDomain subclass, |
| | | * should read the list of replication servers from the configuration, |
| | | * instantiate a {@link ServerState} then start the publish service |
| | | * by calling |
| | | * {@link #startPublishService(Collection, ServerState, int, long)}. |
| | | * At this point it can start calling the {@link #publish(UpdateMsg)} |
| | | * method if needed. |
| | | * <p> |
| | | * When the startup phase reach the point when the subclass is ready |
| | | * to handle updates the Replication Domain implementation should call the |
| | | * {@link #startListenService()} method. |
| | | * At this point a Listener thread is created on the Replication Service |
| | | * and which can start receiving updates. |
| | | * <p> |
| | | * When updates are received the Replication Service calls the |
| | | * {@link #processUpdate(UpdateMsg)} method. |
| | | * ReplicationDomain implementation should implement the appropriate code |
| | | * for replaying the update on the local repository. |
| | | * When fully done the subclass must call the |
| | | * {@link #processUpdateDone(UpdateMsg)} method. |
| | | * This allows to process the update asynchronously if necessary. |
| | | * |
| | | * <p> |
| | | * To propagate changes to other replica, a ReplicationDomain implementation |
| | | * must use the {@link #publish(UpdateMsg)} method. |
| | | * <p> |
| | | * If the Full Initialization process is needed then implementation |
| | | * for {@link #importBackend(InputStream)} and |
| | | * {@link #exportBackend(OutputStream)} must be |
| | | * provided. |
| | | * <p> |
| | | * Full Initialization of a replica can be triggered by LDAP clients |
| | | * by creating InitializeTasks or InitializeTargetTask. |
| | | * Full initialization can also by triggered from the ReplicationDomain |
| | | * implementation using methods {@link #initializeRemote(short, Task)} |
| | | * or {@link #initializeFromRemote(short, Task)}. |
| | | * <p> |
| | | * At shutdown time, the {@link #stopDomain()} method should be called to |
| | | * cleanly stop the replication service. |
| | | */ |
| | | public abstract class ReplicationDomain |
| | | { |
| | | /** |
| | | * Current status for this replicated domain. |
| | | */ |
| | | private ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS; |
| | | |
| | | /** |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | /** |
| | | * An identifier for the Replication Service. |
| | | * All Replication Domain using this identifier will be connected |
| | | * through the Replication Service. |
| | | */ |
| | | private final String serviceID; |
| | | |
| | | /** |
| | | * The identifier of this Replication Domain inside the |
| | | * Replication Service. |
| | | * Each Domain must use a unique ServerID. |
| | | */ |
| | | private final short serverID; |
| | | |
| | | /** |
| | | * The ReplicationBroker that is used by this ReplicationDomain to |
| | | * connect to the ReplicationService. |
| | | */ |
| | | private ReplicationBroker broker; |
| | | |
| | | /** |
| | | * This Map is used to store all outgoing assured messages in order |
| | | * to be able to correlate all the coming back acks to the original |
| | | * operation. |
| | | */ |
| | | private final SortedMap<ChangeNumber, UpdateMsg> waitingAckMsgs = |
| | | new TreeMap<ChangeNumber, UpdateMsg>(); |
| | | |
| | | |
| | | /** |
| | | * The context related to an import or export being processed |
| | | * Null when none is being processed. |
| | | */ |
| | | private IEContext ieContext = null; |
| | | |
| | | /** |
| | | * The Thread waiting for incoming update messages for this domain and pushing |
| | | * them to the global incoming update message queue for later processing by |
| | | * replay threads. |
| | | */ |
| | | private ListenerThread listenerThread; |
| | | |
| | | /** |
| | | * A Map used to store all the ReplicationDomains created on this server. |
| | | */ |
| | | private static Map<String, ReplicationDomain> domains = |
| | | new HashMap<String, ReplicationDomain>(); |
| | | |
| | | /** |
| | | * The Monitor in charge of replication monitoring. |
| | | */ |
| | | private ReplicationMonitor monitor; |
| | | |
| | | /* |
| | | * Assured mode properties |
| | | */ |
| | | // Is assured mode enabled or not for this domain ? |
| | | private boolean assured = false; |
| | | // Assured sub mode (used when assured is true) |
| | | private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE; |
| | | // Safe Data level (used when assuredMode is SAFE_DATA) |
| | | private byte assuredSdLevel = (byte)1; |
| | | // The timeout in ms that should be used, when waiting for assured acks |
| | | private long assuredTimeout = 1000; |
| | | |
| | | // Group id |
| | | private byte groupId = (byte)1; |
| | | // Referrals urls to be published to other servers of the topology |
| | | // TODO: fill that with all currently opened urls if no urls configured |
| | | private List<String> refUrls = new ArrayList<String>(); |
| | | |
| | | /** |
| | | * A set of counters used for Monitoring. |
| | | */ |
| | | private AtomicInteger numProcessedUpdates = new AtomicInteger(); |
| | | private AtomicInteger numRcvdUpdates = new AtomicInteger(0); |
| | | private AtomicInteger numSentUpdates = new AtomicInteger(0); |
| | | |
| | | /* Assured replication monitoring counters */ |
| | | |
| | | // Number of updates sent in Assured Mode, Safe Read |
| | | private AtomicInteger assuredSrSentUpdates = new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Read, that have been |
| | | // successfully acknowledged |
| | | private AtomicInteger assuredSrAcknowledgedUpdates = new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Read, that have not been |
| | | // successfully acknowledged (either because of timeout, wrong status or error |
| | | // at replay) |
| | | private AtomicInteger assuredSrNotAcknowledgedUpdates = |
| | | new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Read, that have not been |
| | | // successfully acknowledged because of timeout |
| | | private AtomicInteger assuredSrTimeoutUpdates = new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Read, that have not been |
| | | // successfully acknowledged because of wrong status |
| | | private AtomicInteger assuredSrWrongStatusUpdates = new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Read, that have not been |
| | | // successfully acknowledged because of replay error |
| | | private AtomicInteger assuredSrReplayErrorUpdates = new AtomicInteger(0); |
| | | // Multiple values allowed: number of updates sent in Assured Mode, Safe Read, |
| | | // that have not been successfully acknowledged (either because of timeout, |
| | | // wrong status or error at replay) for a particular server (DS or RS). String |
| | | // format: <server id>:<number of failed updates> |
| | | private Map<Short,Integer> assuredSrServerNotAcknowledgedUpdates = |
| | | new HashMap<Short,Integer>(); |
| | | // Number of updates sent in Assured Mode, Safe Data |
| | | private AtomicInteger assuredSdSentUpdates = new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Data, that have been |
| | | // successfully acknowledged |
| | | private AtomicInteger assuredSdAcknowledgedUpdates = new AtomicInteger(0); |
| | | // Number of updates sent in Assured Mode, Safe Data, that have not been |
| | | // successfully acknowledged because of timeout |
| | | private AtomicInteger assuredSdTimeoutUpdates = new AtomicInteger(0); |
| | | // Multiple values allowed: number of updates sent in Assured Mode, Safe Data, |
| | | // that have not been successfully acknowledged because of timeout for a |
| | | // particular RS. String format: <server id>:<number of failed updates> |
| | | private Map<Short,Integer> assuredSdServerTimeoutUpdates = |
| | | new HashMap<Short,Integer>(); |
| | | |
| | | /* Status related monitoring fields */ |
| | | |
| | | // Indicates the date when the status changed. This may be used to indicate |
| | | // the date the session with the current replication server started (when |
| | | // status is NORMAL for instance). All the above assured monitoring fields |
| | | // are also resetted each time the status is changed |
| | | private Date lastStatusChangeDate = new Date(); |
| | | |
| | | /** |
| | | * The state maintained by the Concrete Class. |
| | | */ |
| | | private final ServerState state; |
| | | |
| | | /** |
| | | * The generator that will be used to generate {@link ChangeNumber} |
| | | * for this domain. |
| | | */ |
| | | private final ChangeNumberGenerator generator; |
| | | |
| | | /** |
| | | * Returns the {@link ChangeNumberGenerator} that will be used to |
| | | * generate {@link ChangeNumber} for this domain. |
| | | * |
| | | * @return The {@link ChangeNumberGenerator} that will be used to |
| | | * generate {@link ChangeNumber} for this domain. |
| | | */ |
| | | public ChangeNumberGenerator getGenerator() |
| | | { |
| | | return generator; |
| | | } |
| | | |
| | | /** |
| | | * Creates a ReplicationDomain with the provided parameters. |
| | | * |
| | | * @param serviceID The identifier of the Replication Domain to which |
| | | * this object is participating. |
| | | * @param serverID The identifier of the server that is participating |
| | | * to the Replication Domain. |
| | | * This identifier should be different for each server that |
| | | * is participating to a given Replication Domain. |
| | | */ |
| | | public ReplicationDomain(String serviceID, short serverID) |
| | | { |
| | | this.serviceID = serviceID; |
| | | this.serverID = serverID; |
| | | this.state = new ServerState(); |
| | | this.generator = new ChangeNumberGenerator(serverID, state); |
| | | |
| | | domains.put(serviceID, this); |
| | | } |
| | | |
| | | /** |
| | | * Set the initial status of the domain and perform necessary initializations. |
| | | * This method will be called by the Broker each time the ReplicationBroker |
| | | * establish a new session to a Replication Server. |
| | | * |
| | | * Implementations may override this method when they need to perform |
| | | * additional computing after session establishment. |
| | | * The default implementation should be sufficient for ReplicationDomains |
| | | * that don't need to perform additional computing. |
| | | * |
| | | * @param initStatus The status to enter the state machine with. |
| | | * @param replicationServerState The ServerState of the ReplicationServer |
| | | * with which the session was established. |
| | | * @param session The ProtocolSession that is currently used. |
| | | */ |
| | | public void sessionInitiated( |
| | | ServerStatus initStatus, |
| | | ServerState replicationServerState, |
| | | ProtocolSession session) |
| | | { |
| | | // Sanity check: is it a valid initial status? |
| | | if (!isValidInitialStatus(initStatus)) |
| | | { |
| | | Message msg = ERR_DS_INVALID_INIT_STATUS.get(initStatus.toString(), |
| | | serviceID, Short.toString(serverID)); |
| | | logError(msg); |
| | | } else |
| | | { |
| | | status = initStatus; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Processes an incoming ChangeStatusMsg. Compute new status according to |
| | | * given order. Then update domain for being compliant with new status |
| | | * definition. |
| | | * @param csMsg The received status message |
| | | */ |
| | | private void receiveChangeStatus(ChangeStatusMsg csMsg) |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + serviceID + |
| | | " received change status message:\n" + csMsg); |
| | | |
| | | ServerStatus reqStatus = csMsg.getRequestedStatus(); |
| | | |
| | | // Translate requested status to a state machine event |
| | | StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus); |
| | | if (event == StatusMachineEvent.INVALID_EVENT) |
| | | { |
| | | Message msg = ERR_DS_INVALID_REQUESTED_STATUS.get(reqStatus.toString(), |
| | | serviceID, Short.toString(serverID)); |
| | | logError(msg); |
| | | return; |
| | | } |
| | | |
| | | // Set the new status to the requested one |
| | | setNewStatus(event); |
| | | } |
| | | |
| | | /** |
| | | * Called when first connection or disconnection detected. |
| | | */ |
| | | void toNotConnectedStatus() |
| | | { |
| | | // Go into not connected status |
| | | setNewStatus(StatusMachineEvent.TO_NOT_CONNECTED_STATUS_EVENT); |
| | | } |
| | | |
| | | /** |
| | | * Perform whatever actions are needed to apply properties for being |
| | | * compliant with new status. Must be called in synchronized section for |
| | | * status. The new status is already set in status variable. |
| | | */ |
| | | private void updateDomainForNewStatus() |
| | | { |
| | | switch (status) |
| | | { |
| | | case NOT_CONNECTED_STATUS: |
| | | break; |
| | | case NORMAL_STATUS: |
| | | break; |
| | | case DEGRADED_STATUS: |
| | | break; |
| | | case FULL_UPDATE_STATUS: |
| | | // Signal RS we just entered the full update status |
| | | broker.signalStatusChange(status); |
| | | break; |
| | | case BAD_GEN_ID_STATUS: |
| | | break; |
| | | default: |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("updateDomainForNewStatus: unexpected status: " + |
| | | status); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Gets the status for this domain. |
| | | * @return The status for this domain. |
| | | */ |
| | | public ServerStatus getStatus() |
| | | { |
| | | return status; |
| | | } |
| | | |
| | | /** |
| | | * Gets the identifier of this domain. |
| | | * |
| | | * @return The identifier for this domain. |
| | | */ |
| | | public String getServiceID() |
| | | { |
| | | return serviceID; |
| | | } |
| | | |
| | | /** |
| | | * Get the server ID. |
| | | * @return The server ID. |
| | | */ |
| | | public short getServerId() |
| | | { |
| | | return serverID; |
| | | } |
| | | |
| | | /** |
| | | * Tells if assured replication is enabled for this domain. |
| | | * @return True if assured replication is enabled for this domain. |
| | | */ |
| | | public boolean isAssured() |
| | | { |
| | | return assured; |
| | | } |
| | | |
| | | /** |
| | | * Gives the mode for the assured replication of the domain. |
| | | * @return The mode for the assured replication of the domain. |
| | | */ |
| | | public AssuredMode getAssuredMode() |
| | | { |
| | | return assuredMode; |
| | | } |
| | | |
| | | /** |
| | | * Gives the assured level of the replication of the domain. |
| | | * @return The assured level of the replication of the domain. |
| | | */ |
| | | public byte getAssuredSdLevel() |
| | | { |
| | | return assuredSdLevel; |
| | | } |
| | | |
| | | /** |
| | | * Gives the assured timeout of the replication of the domain (in ms). |
| | | * @return The assured timeout of the replication of the domain. |
| | | */ |
| | | public long getAssuredTimeout() |
| | | { |
| | | return assuredTimeout; |
| | | } |
| | | |
| | | /** |
| | | * Gets the group id for this domain. |
| | | * @return The group id for this domain. |
| | | */ |
| | | public byte getGroupId() |
| | | { |
| | | return groupId; |
| | | } |
| | | |
| | | /** |
| | | * Gets the referrals URLs this domain publishes. |
| | | * @return The referrals URLs this domain publishes. |
| | | */ |
| | | public List<String> getRefUrls() |
| | | { |
| | | return refUrls; |
| | | } |
| | | |
| | | /** |
| | | * Gets the info for DSs in the topology (except us). |
| | | * @return The info for DSs in the topology (except us) |
| | | */ |
| | | public List<DSInfo> getDsList() |
| | | { |
| | | return broker.getDsList(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the info for RSs in the topology (except the one we are connected |
| | | * to). |
| | | * @return The info for RSs in the topology (except the one we are connected |
| | | * to) |
| | | */ |
| | | public List<RSInfo> getRsList() |
| | | { |
| | | return broker.getRsList(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Gets the server ID of the Replication Server to which the domain |
| | | * is currently connected. |
| | | * |
| | | * @return The server ID of the Replication Server to which the domain |
| | | * is currently connected. |
| | | */ |
| | | public short getRsServerId() |
| | | { |
| | | return broker.getRsServerId(); |
| | | } |
| | | |
| | | /** |
| | | * Increment the number of processed updates. |
| | | */ |
| | | private void incProcessedUpdates() |
| | | { |
| | | numProcessedUpdates.incrementAndGet(); |
| | | } |
| | | |
| | | /** |
| | | * get the number of updates replayed by the replication. |
| | | * |
| | | * @return The number of updates replayed by the replication |
| | | */ |
| | | int getNumProcessedUpdates() |
| | | { |
| | | if (numProcessedUpdates != null) |
| | | return numProcessedUpdates.get(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * get the number of updates received by the replication plugin. |
| | | * |
| | | * @return the number of updates received |
| | | */ |
| | | int getNumRcvdUpdates() |
| | | { |
| | | if (numRcvdUpdates != null) |
| | | return numRcvdUpdates.get(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the number of updates sent by the replication plugin. |
| | | * |
| | | * @return the number of updates sent |
| | | */ |
| | | int getNumSentUpdates() |
| | | { |
| | | if (numSentUpdates != null) |
| | | return numSentUpdates.get(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Set the list of Referrals that should be returned when an |
| | | * operation needs to be redirected to this server. |
| | | * |
| | | * @param referralsUrl The list of referrals. |
| | | */ |
| | | public void setURLs(Set<String> referralsUrl) |
| | | { |
| | | for (String url : referralsUrl) |
| | | this.refUrls.add(url); |
| | | } |
| | | |
| | | /** |
| | | * Set the timeout of the assured replication. |
| | | * |
| | | * @param assuredTimeout the timeout of the assured replication. |
| | | */ |
| | | public void setAssuredTimeout(long assuredTimeout) |
| | | { |
| | | this.assuredTimeout = assuredTimeout; |
| | | } |
| | | |
| | | /** |
| | | * Sets the groupID. |
| | | * |
| | | * @param groupId The groupID. |
| | | */ |
| | | public void setGroupId(byte groupId) |
| | | { |
| | | this.groupId = groupId; |
| | | } |
| | | |
| | | /** |
| | | * Sets the level of assured replication. |
| | | * |
| | | * @param assuredSdLevel The level of assured replication. |
| | | */ |
| | | public void setAssuredSdLevel(byte assuredSdLevel) |
| | | { |
| | | this.assuredSdLevel = assuredSdLevel; |
| | | } |
| | | |
| | | /** |
| | | * Sets the assured replication mode. |
| | | * |
| | | * @param dataMode The assured replication mode. |
| | | */ |
| | | public void setAssuredMode(AssuredMode dataMode) |
| | | { |
| | | this.assuredMode = dataMode; |
| | | } |
| | | |
| | | /** |
| | | * Sets assured replication. |
| | | * |
| | | * @param assured A boolean indicating if assured replication should be used. |
| | | */ |
| | | public void setAssured(boolean assured) |
| | | { |
| | | this.assured = assured; |
| | | } |
| | | |
| | | /** |
| | | * Receives an update message from the replicationServer. |
| | | * also responsible for updating the list of pending changes |
| | | * @return the received message - null if none |
| | | */ |
| | | UpdateMsg receive() |
| | | { |
| | | UpdateMsg update = null; |
| | | |
| | | while (update == null) |
| | | { |
| | | InitializeRequestMsg initMsg = null; |
| | | ReplicationMsg msg; |
| | | try |
| | | { |
| | | msg = broker.receive(); |
| | | if (msg == null) |
| | | { |
| | | // The server is in the shutdown process |
| | | return null; |
| | | } |
| | | |
| | | if (debugEnabled()) |
| | | if (!(msg instanceof HeartbeatMsg)) |
| | | TRACER.debugVerbose("Message received <" + msg + ">"); |
| | | |
| | | if (msg instanceof AckMsg) |
| | | { |
| | | AckMsg ack = (AckMsg) msg; |
| | | receiveAck(ack); |
| | | } |
| | | else if (msg instanceof InitializeRequestMsg) |
| | | { |
| | | // Another server requests us to provide entries |
| | | // for a total update |
| | | initMsg = (InitializeRequestMsg)msg; |
| | | } |
| | | else if (msg instanceof InitializeTargetMsg) |
| | | { |
| | | // Another server is exporting its entries to us |
| | | InitializeTargetMsg importMsg = (InitializeTargetMsg) msg; |
| | | |
| | | try |
| | | { |
| | | // This must be done while we are still holding the |
| | | // broker lock because we are now going to receive a |
| | | // bunch of entries from the remote server and we |
| | | // want the import thread to catch them and |
| | | // not the ListenerThread. |
| | | initialize(importMsg); |
| | | } |
| | | catch(DirectoryException de) |
| | | { |
| | | // Returns an error message to notify the sender |
| | | ErrorMsg errorMsg = |
| | | new ErrorMsg(importMsg.getsenderID(), |
| | | de.getMessageObject()); |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(de.getMessageObject()); |
| | | TRACER.debugInfo(Message.toString(mb.toMessage())); |
| | | broker.publish(errorMsg); |
| | | } |
| | | } |
| | | else if (msg instanceof ErrorMsg) |
| | | { |
| | | if (ieContext != null) |
| | | { |
| | | // This is an error termination for the 2 following cases : |
| | | // - either during an export |
| | | // - or before an import really started |
| | | // For example, when we publish a request and the |
| | | // replicationServer did not find any import source. |
| | | abandonImportExport((ErrorMsg)msg); |
| | | } |
| | | else |
| | | { |
| | | /* |
| | | * Log error message |
| | | */ |
| | | ErrorMsg errorMsg = (ErrorMsg)msg; |
| | | logError(ERR_ERROR_MSG_RECEIVED.get( |
| | | errorMsg.getDetails())); |
| | | } |
| | | } |
| | | else if (msg instanceof ChangeStatusMsg) |
| | | { |
| | | ChangeStatusMsg csMsg = (ChangeStatusMsg)msg; |
| | | receiveChangeStatus(csMsg); |
| | | } |
| | | else if (msg instanceof UpdateMsg) |
| | | { |
| | | generator.adjust(((UpdateMsg) msg).getChangeNumber()); |
| | | update = (UpdateMsg) msg; |
| | | generator.adjust(update.getChangeNumber()); |
| | | } |
| | | } |
| | | catch (SocketTimeoutException e) |
| | | { |
| | | // just retry |
| | | } |
| | | // Test if we have received and export request message and |
| | | // if that's the case handle it now. |
| | | // This must be done outside of the portion of code protected |
| | | // by the broker lock so that we keep receiveing update |
| | | // when we are doing and export and so that a possible |
| | | // closure of the socket happening when we are publishing the |
| | | // entries to the remote can be handled by the other |
| | | // replay thread when they call this method and therefore the |
| | | // broker.receive() method. |
| | | if (initMsg != null) |
| | | { |
| | | // Do this work in a thread to allow replay thread continue working |
| | | ExportThread exportThread = new ExportThread(initMsg.getsenderID()); |
| | | exportThread.start(); |
| | | } |
| | | } |
| | | |
| | | numRcvdUpdates.incrementAndGet(); |
| | | return update; |
| | | } |
| | | |
| | | /** |
| | | * Wait for the processing of an assured message. |
| | | * |
| | | * @param msg The UpdateMsg for which we are waiting for an ack. |
| | | * @throws TimeoutException When the configured timeout occurs waiting for the |
| | | * ack. |
| | | */ |
| | | private void waitForAck(UpdateMsg msg) throws TimeoutException |
| | | { |
| | | // Wait for the ack to be received, timing out if necessary |
| | | long startTime = System.currentTimeMillis(); |
| | | synchronized (msg) |
| | | { |
| | | ChangeNumber cn = msg.getChangeNumber(); |
| | | while (waitingAckMsgs.containsKey(cn)) |
| | | { |
| | | try |
| | | { |
| | | // WARNING: this timeout may be difficult to optimize: too low, it |
| | | // may use too much CPU, too high, it may penalize performance... |
| | | msg.wait(10); |
| | | } catch (InterruptedException e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("waitForAck method interrupted for replication " + |
| | | "serviceID: " + serviceID); |
| | | } |
| | | break; |
| | | } |
| | | // Timeout ? |
| | | if ( (System.currentTimeMillis() - startTime) >= assuredTimeout ) |
| | | { |
| | | // Timeout occured, be sure that ack is not being received and if so, |
| | | // remove the update from the wait list, log the timeout error and |
| | | // also update assured monitoring counters |
| | | UpdateMsg update; |
| | | synchronized (waitingAckMsgs) |
| | | { |
| | | update = waitingAckMsgs.remove(cn); |
| | | } |
| | | |
| | | if (update != null) |
| | | { |
| | | // No luck, this is a real timeout |
| | | // Increment assured replication monitoring counters |
| | | switch (msg.getAssuredMode()) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | assuredSrNotAcknowledgedUpdates.incrementAndGet(); |
| | | assuredSrTimeoutUpdates.incrementAndGet(); |
| | | // Increment number of errors for our RS |
| | | updateAssuredErrorsByServer( |
| | | assuredSrServerNotAcknowledgedUpdates, |
| | | broker.getRsServerId()); |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | assuredSdTimeoutUpdates.incrementAndGet(); |
| | | // Increment number of errors for our RS |
| | | updateAssuredErrorsByServer(assuredSdServerTimeoutUpdates, |
| | | broker.getRsServerId()); |
| | | break; |
| | | default: |
| | | // Should no happen |
| | | } |
| | | |
| | | throw new TimeoutException("No ack received for message cn: " + cn + |
| | | " and replication servceID: " + serviceID + " after " + |
| | | assuredTimeout + " ms."); |
| | | } else |
| | | { |
| | | // Ack received just before timeout limit: we can exit |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Updates the passed monitoring list of errors received for assured messages |
| | | * (safe data or safe read, depending of the passed list to update) for a |
| | | * particular server in the list. This increments the counter of error for the |
| | | * passed server, or creates an initial value of 1 error for it if the server |
| | | * is not yet present in the map. |
| | | * @param errorList |
| | | * @param serverId |
| | | */ |
| | | private void updateAssuredErrorsByServer(Map<Short,Integer> errorsByServer, |
| | | Short serverId) |
| | | { |
| | | synchronized (errorsByServer) |
| | | { |
| | | Integer serverErrCount = errorsByServer.get(serverId); |
| | | if (serverErrCount == null) |
| | | { |
| | | // Server not present in list, create an entry with an |
| | | // initial number of errors set to 1 |
| | | errorsByServer.put(serverId, 1); |
| | | } else |
| | | { |
| | | // Server already present in list, just increment number of |
| | | // errors for the server |
| | | int val = serverErrCount.intValue(); |
| | | val++; |
| | | errorsByServer.put(serverId, val); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Do the necessary processing when an AckMsg is received. |
| | | * |
| | | * @param ack The AckMsg that was received. |
| | | */ |
| | | private void receiveAck(AckMsg ack) |
| | | { |
| | | UpdateMsg update; |
| | | ChangeNumber changeNumber = ack.getChangeNumber(); |
| | | |
| | | // Remove the message for pending ack list (this may already make the thread |
| | | // that is waiting for the ack be aware of its reception) |
| | | synchronized (waitingAckMsgs) |
| | | { |
| | | update = waitingAckMsgs.remove(changeNumber); |
| | | } |
| | | |
| | | // Signal waiting thread ack has been received |
| | | if (update != null) |
| | | { |
| | | synchronized (update) |
| | | { |
| | | update.notify(); |
| | | } |
| | | |
| | | // Analyze status of embedded in the ack to see if everything went well |
| | | boolean hasTimeout = ack.hasTimeout(); |
| | | boolean hasReplayErrors = ack.hasReplayError(); |
| | | boolean hasWrongStatus = ack.hasWrongStatus(); |
| | | |
| | | AssuredMode updateAssuredMode = update.getAssuredMode(); |
| | | |
| | | if ( hasTimeout || hasReplayErrors || hasWrongStatus) |
| | | { |
| | | // Some problems detected: message not correclty reached every requested |
| | | // servers. Log problem |
| | | Message errorMsg = ERR_DS_RECEIVED_ACK_ERROR.get(serviceID, |
| | | update.toString(), ack.errorsToString()); |
| | | logError(errorMsg); |
| | | |
| | | List<Short> failedServers = ack.getFailedServers(); |
| | | |
| | | // Increment assured replication monitoring counters |
| | | switch (updateAssuredMode) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | assuredSrNotAcknowledgedUpdates.incrementAndGet(); |
| | | if (hasTimeout) |
| | | assuredSrTimeoutUpdates.incrementAndGet(); |
| | | if (hasReplayErrors) |
| | | assuredSrReplayErrorUpdates.incrementAndGet(); |
| | | if (hasWrongStatus) |
| | | assuredSrWrongStatusUpdates.incrementAndGet(); |
| | | if (failedServers != null) // This should always be the case ! |
| | | { |
| | | for(Short sid : failedServers) |
| | | { |
| | | updateAssuredErrorsByServer( |
| | | assuredSrServerNotAcknowledgedUpdates, sid); |
| | | } |
| | | } |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | // The only possible cause of ack error in safe data mode is timeout |
| | | if (hasTimeout) // So should always be the case |
| | | assuredSdTimeoutUpdates.incrementAndGet(); |
| | | if (failedServers != null) // This should always be the case ! |
| | | { |
| | | for(Short sid : failedServers) |
| | | { |
| | | updateAssuredErrorsByServer( |
| | | assuredSdServerTimeoutUpdates, sid); |
| | | } |
| | | } |
| | | break; |
| | | default: |
| | | // Should no happen |
| | | } |
| | | } else |
| | | { |
| | | // Update has been acknowledged without errors |
| | | // Increment assured replication monitoring counters |
| | | switch (updateAssuredMode) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | assuredSrAcknowledgedUpdates.incrementAndGet(); |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | assuredSdAcknowledgedUpdates.incrementAndGet(); |
| | | break; |
| | | default: |
| | | // Should no happen |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Retrieves a replication domain based on the baseDn. |
| | | * |
| | | * @param serviceID The identifier of the domain to retrieve. |
| | | * |
| | | * @return The domain retrieved. |
| | | * |
| | | * @throws DirectoryException When an error occurred or no domain |
| | | * match the provided baseDn. |
| | | */ |
| | | static ReplicationDomain retrievesReplicationDomain(String serviceID) |
| | | throws DirectoryException |
| | | { |
| | | ReplicationDomain replicationDomain = domains.get(serviceID); |
| | | if (replicationDomain == null) |
| | | { |
| | | MessageBuilder mb = new MessageBuilder(ERR_NO_MATCHING_DOMAIN.get()); |
| | | mb.append(" "); |
| | | mb.append(serviceID); |
| | | throw new DirectoryException(ResultCode.OTHER, |
| | | mb.toMessage()); |
| | | } |
| | | return replicationDomain; |
| | | } |
| | | |
| | | /* |
| | | * After this point the code is related to the Total Update. |
| | | */ |
| | | |
| | | /** |
| | | * This thread is launched when we want to export data to another server that |
| | | * has requested to be initialized with the data of our backend. |
| | | */ |
| | | private class ExportThread extends DirectoryThread |
| | | { |
| | | // Id of server that will receive updates |
| | | private short target; |
| | | |
| | | /** |
| | | * Constructor for the ExportThread. |
| | | * |
| | | * @param target Id of server that will receive updates |
| | | */ |
| | | public ExportThread(short target) |
| | | { |
| | | super("Export thread " + serverID); |
| | | this.target = target; |
| | | } |
| | | |
| | | /** |
| | | * Run method for this class. |
| | | */ |
| | | public void run() |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("Export thread starting."); |
| | | } |
| | | |
| | | try |
| | | { |
| | | initializeRemote(target, target, null); |
| | | } catch (DirectoryException de) |
| | | { |
| | | // An error message has been sent to the peer |
| | | // Nothing more to do locally |
| | | } |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("Export thread stopping."); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * This class contain the context related to an import or export |
| | | * launched on the domain. |
| | | */ |
| | | private class IEContext |
| | | { |
| | | // The task that initiated the operation. |
| | | Task initializeTask; |
| | | // The input stream for the import |
| | | ReplInputStream ldifImportInputStream = null; |
| | | // The target in the case of an export |
| | | short exportTarget = RoutableMsg.UNKNOWN_SERVER; |
| | | // The source in the case of an import |
| | | short importSource = RoutableMsg.UNKNOWN_SERVER; |
| | | |
| | | // The total entry count expected to be processed |
| | | long entryCount = 0; |
| | | // The count for the entry not yet processed |
| | | long entryLeftCount = 0; |
| | | |
| | | // The exception raised when any |
| | | DirectoryException exception = null; |
| | | |
| | | // A boolean indicating if the context is related to an |
| | | // import or an export. |
| | | boolean importInProgress; |
| | | |
| | | /** |
| | | * Creates a new IEContext. |
| | | * |
| | | * @param importInProgress true if the IEContext will be used |
| | | * for and import, false if the IEContext |
| | | * will be used for and export. |
| | | */ |
| | | public IEContext(boolean importInProgress) |
| | | { |
| | | this.importInProgress = importInProgress; |
| | | } |
| | | |
| | | /** |
| | | * Initializes the import/export counters with the provider value. |
| | | * @param total |
| | | * @param left |
| | | * @throws DirectoryException |
| | | */ |
| | | public void setCounters(long total, long left) |
| | | throws DirectoryException |
| | | { |
| | | entryCount = total; |
| | | entryLeftCount = left; |
| | | |
| | | if (initializeTask != null) |
| | | { |
| | | if (initializeTask instanceof InitializeTask) |
| | | { |
| | | ((InitializeTask)initializeTask).setTotal(entryCount); |
| | | ((InitializeTask)initializeTask).setLeft(entryCount); |
| | | } |
| | | else if (initializeTask instanceof InitializeTargetTask) |
| | | { |
| | | ((InitializeTargetTask)initializeTask).setTotal(entryCount); |
| | | ((InitializeTargetTask)initializeTask).setLeft(entryCount); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update the counters of the task for each entry processed during |
| | | * an import or export. |
| | | * @throws DirectoryException |
| | | */ |
| | | public void updateCounters() |
| | | throws DirectoryException |
| | | { |
| | | entryLeftCount--; |
| | | |
| | | if (initializeTask != null) |
| | | { |
| | | if (initializeTask instanceof InitializeTask) |
| | | { |
| | | ((InitializeTask)initializeTask).setLeft(entryLeftCount); |
| | | } |
| | | else if (initializeTask instanceof InitializeTargetTask) |
| | | { |
| | | ((InitializeTargetTask)initializeTask).setLeft(entryLeftCount); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update the counters of the task for each entry processed during |
| | | * an import or export. |
| | | * |
| | | * @param entriesDone The number of entries that were processed |
| | | * since the last time this method was called. |
| | | * |
| | | * @throws DirectoryException |
| | | */ |
| | | public void updateCounters(int entriesDone) |
| | | throws DirectoryException |
| | | { |
| | | entryLeftCount -= entriesDone; |
| | | |
| | | if (initializeTask != null) |
| | | { |
| | | if (initializeTask instanceof InitializeTask) |
| | | { |
| | | ((InitializeTask)initializeTask).setLeft(entryLeftCount); |
| | | } |
| | | else if (initializeTask instanceof InitializeTargetTask) |
| | | { |
| | | ((InitializeTargetTask)initializeTask).setLeft(entryLeftCount); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public String toString() |
| | | { |
| | | return new String("[ Entry count=" + this.entryCount + |
| | | ", Entry left count=" + this.entryLeftCount + "]"); |
| | | } |
| | | } |
| | | /** |
| | | * Verifies that the given string represents a valid source |
| | | * from which this server can be initialized. |
| | | * |
| | | * @param targetString The string representing the source |
| | | * @return The source as a short value |
| | | * @throws DirectoryException if the string is not valid |
| | | */ |
| | | short decodeTarget(String targetString) |
| | | throws DirectoryException |
| | | { |
| | | short target = 0; |
| | | Throwable cause; |
| | | if (targetString.equalsIgnoreCase("all")) |
| | | { |
| | | return RoutableMsg.ALL_SERVERS; |
| | | } |
| | | |
| | | // So should be a serverID |
| | | try |
| | | { |
| | | target = Integer.decode(targetString).shortValue(); |
| | | if (target >= 0) |
| | | { |
| | | // FIXME Could we check now that it is a know server in the domain ? |
| | | } |
| | | return target; |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | cause = e; |
| | | } |
| | | ResultCode resultCode = ResultCode.OTHER; |
| | | Message message = ERR_INVALID_EXPORT_TARGET.get(); |
| | | |
| | | if (cause != null) |
| | | throw new DirectoryException( |
| | | resultCode, message, cause); |
| | | else |
| | | throw new DirectoryException( |
| | | resultCode, message); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * Process the initialization of some other server or servers in the topology |
| | | * specified by the target argument. |
| | | * @param target The target that should be initialized |
| | | * @param initTask The task that triggers this initialization and that should |
| | | * be updated with its progress. |
| | | * |
| | | * @exception DirectoryException When an error occurs. |
| | | */ |
| | | void initializeRemote(short target, Task initTask) |
| | | throws DirectoryException |
| | | { |
| | | initializeRemote(target, serverID, initTask); |
| | | } |
| | | |
| | | /** |
| | | * Process the initialization of some other server or servers in the topology |
| | | * specified by the target argument when this initialization specifying the |
| | | * server that requests the initialization. |
| | | * |
| | | * @param target The target that should be initialized. |
| | | * @param requestorID The server that initiated the export. |
| | | * @param initTask The task that triggers this initialization and that should |
| | | * be updated with its progress. |
| | | * |
| | | * @exception DirectoryException When an error occurs. |
| | | */ |
| | | void initializeRemote(short target, short requestorID, Task initTask) |
| | | throws DirectoryException |
| | | { |
| | | Message msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START.get( |
| | | Short.toString(serverID), |
| | | serviceID, |
| | | Short.toString(requestorID)); |
| | | logError(msg); |
| | | |
| | | boolean contextAcquired=false; |
| | | |
| | | acquireIEContext(false); |
| | | contextAcquired = true; |
| | | ieContext.exportTarget = target; |
| | | |
| | | if (initTask != null) |
| | | { |
| | | ieContext.initializeTask = initTask; |
| | | } |
| | | |
| | | // The number of entries to be exported is the number of entries under |
| | | // the base DN entry and the base entry itself. |
| | | long entryCount = this.countEntries(); |
| | | |
| | | |
| | | ieContext.setCounters(entryCount, entryCount); |
| | | |
| | | // Send start message to the peer |
| | | InitializeTargetMsg initializeMessage = new InitializeTargetMsg( |
| | | serviceID, serverID, target, requestorID, entryCount); |
| | | |
| | | broker.publish(initializeMessage); |
| | | |
| | | try |
| | | { |
| | | exportBackend(new BufferedOutputStream(new ReplOutputStream(this))); |
| | | |
| | | // Notify the peer of the success |
| | | DoneMsg doneMsg = new DoneMsg(serverID, |
| | | initializeMessage.getDestination()); |
| | | broker.publish(doneMsg); |
| | | |
| | | releaseIEContext(); |
| | | } |
| | | catch(DirectoryException de) |
| | | { |
| | | // Notify the peer of the failure |
| | | ErrorMsg errorMsg = |
| | | new ErrorMsg(target, |
| | | de.getMessageObject()); |
| | | broker.publish(errorMsg); |
| | | |
| | | if (contextAcquired) |
| | | releaseIEContext(); |
| | | |
| | | throw(de); |
| | | } |
| | | |
| | | msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_END.get( |
| | | Short.toString(serverID), |
| | | serviceID, |
| | | Short.toString(requestorID)); |
| | | logError(msg); |
| | | } |
| | | |
| | | /** |
| | | * Get the ServerState maintained by the Concrete class. |
| | | * |
| | | * @return the ServerState maintained by the Concrete class. |
| | | */ |
| | | public ServerState getServerState() |
| | | { |
| | | return state; |
| | | } |
| | | |
| | | |
| | | private synchronized void acquireIEContext(boolean importInProgress) |
| | | throws DirectoryException |
| | | { |
| | | if (ieContext != null) |
| | | { |
| | | // Rejects 2 simultaneous exports |
| | | Message message = ERR_SIMULTANEOUS_IMPORT_EXPORT_REJECTED.get(); |
| | | throw new DirectoryException(ResultCode.OTHER, |
| | | message); |
| | | } |
| | | |
| | | ieContext = new IEContext(importInProgress); |
| | | } |
| | | |
| | | private synchronized void releaseIEContext() |
| | | { |
| | | ieContext = null; |
| | | } |
| | | |
| | | /** |
| | | * Processes an error message received while an import/export is |
| | | * on going. |
| | | * @param errorMsg The error message received. |
| | | */ |
| | | void abandonImportExport(ErrorMsg errorMsg) |
| | | { |
| | | // FIXME TBD Treat the case where the error happens while entries |
| | | // are being exported |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugVerbose( |
| | | " abandonImportExport:" + this.serverID + |
| | | " serviceID: " + this.serviceID + |
| | | " Error Msg received: " + errorMsg); |
| | | |
| | | if (ieContext != null) |
| | | { |
| | | ieContext.exception = new DirectoryException(ResultCode.OTHER, |
| | | errorMsg.getDetails()); |
| | | |
| | | if (ieContext.initializeTask instanceof InitializeTask) |
| | | { |
| | | // Update the task that initiated the import |
| | | ((InitializeTask)ieContext.initializeTask). |
| | | updateTaskCompletionState(ieContext.exception); |
| | | |
| | | releaseIEContext(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Receives bytes related to an entry in the context of an import to |
| | | * initialize the domain (called by ReplLDIFInputStream). |
| | | * |
| | | * @return The bytes. Null when the Done or Err message has been received |
| | | */ |
| | | byte[] receiveEntryBytes() |
| | | { |
| | | ReplicationMsg msg; |
| | | while (true) |
| | | { |
| | | try |
| | | { |
| | | msg = broker.receive(); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugVerbose( |
| | | " sid:" + serverID + |
| | | " base DN:" + serviceID + |
| | | " Import EntryBytes received " + msg); |
| | | if (msg == null) |
| | | { |
| | | // The server is in the shutdown process |
| | | return null; |
| | | } |
| | | |
| | | if (msg instanceof EntryMsg) |
| | | { |
| | | EntryMsg entryMsg = (EntryMsg)msg; |
| | | byte[] entryBytes = entryMsg.getEntryBytes(); |
| | | ieContext.updateCounters(countEntryLimits(entryBytes)); |
| | | return entryBytes; |
| | | } |
| | | else if (msg instanceof DoneMsg) |
| | | { |
| | | // This is the normal termination of the import |
| | | // No error is stored and the import is ended |
| | | // by returning null |
| | | return null; |
| | | } |
| | | else if (msg instanceof ErrorMsg) |
| | | { |
| | | // This is an error termination during the import |
| | | // The error is stored and the import is ended |
| | | // by returning null |
| | | ErrorMsg errorMsg = (ErrorMsg)msg; |
| | | ieContext.exception = new DirectoryException( |
| | | ResultCode.OTHER, |
| | | errorMsg.getDetails()); |
| | | return null; |
| | | } |
| | | else |
| | | { |
| | | // Other messages received during an import are trashed |
| | | } |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | // TODO: i18n |
| | | ieContext.exception = new DirectoryException(ResultCode.OTHER, |
| | | Message.raw("received an unexpected message type" + |
| | | e.getLocalizedMessage())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Count the number of entries in the provided byte[]. |
| | | * This is based on the hypothesis that the entries are separated |
| | | * by a "\n\n" String. |
| | | * |
| | | * @param entryBytes |
| | | * @return The number of entries in the provided byte[]. |
| | | */ |
| | | private int countEntryLimits(byte[] entryBytes) |
| | | { |
| | | return countEntryLimits(entryBytes, 0, entryBytes.length); |
| | | } |
| | | |
| | | /** |
| | | * Count the number of entries in the provided byte[]. |
| | | * This is based on the hypothesis that the entries are separated |
| | | * by a "\n\n" String. |
| | | * |
| | | * @param entryBytes |
| | | * @return The number of entries in the provided byte[]. |
| | | */ |
| | | private int countEntryLimits(byte[] entryBytes, int pos, int length) |
| | | { |
| | | int entryCount = 0; |
| | | int count = 0; |
| | | while (count<=length-2) |
| | | { |
| | | if ((entryBytes[pos+count] == '\n') && (entryBytes[pos+count+1] == '\n')) |
| | | { |
| | | entryCount++; |
| | | count++; |
| | | } |
| | | count++; |
| | | } |
| | | return entryCount; |
| | | } |
| | | |
| | | /** |
| | | * Exports an entry in LDIF format. |
| | | * |
| | | * @param lDIFEntry The entry to be exported in byte[] form. |
| | | * @param pos The starting Position in the array. |
| | | * @param length Number of array elements to be copied. |
| | | * |
| | | * @throws IOException when an error occurred. |
| | | */ |
| | | void exportLDIFEntry(byte[] lDIFEntry, int pos, int length) throws IOException |
| | | { |
| | | // If an error was raised - like receiving an ErrorMsg |
| | | // we just let down the export. |
| | | if (ieContext.exception != null) |
| | | { |
| | | IOException ioe = new IOException(ieContext.exception.getMessage()); |
| | | ieContext = null; |
| | | throw ioe; |
| | | } |
| | | |
| | | EntryMsg entryMessage = new EntryMsg( |
| | | serverID, ieContext.exportTarget, lDIFEntry, pos, length); |
| | | broker.publish(entryMessage); |
| | | |
| | | try |
| | | { |
| | | ieContext.updateCounters(countEntryLimits(lDIFEntry, pos, length)); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | throw new IOException(de.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Initializes this domain from another source server. |
| | | * |
| | | * @param source The source from which to initialize |
| | | * @param initTask The task that launched the initialization |
| | | * and should be updated of its progress. |
| | | * @throws DirectoryException when an error occurs |
| | | */ |
| | | void initializeFromRemote(short source, Task initTask) |
| | | throws DirectoryException |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Entering initializeFromRemote"); |
| | | |
| | | acquireIEContext(true); |
| | | ieContext.initializeTask = initTask; |
| | | |
| | | InitializeRequestMsg initializeMsg = new InitializeRequestMsg( |
| | | serviceID, serverID, source); |
| | | |
| | | // Publish Init request msg |
| | | broker.publish(initializeMsg); |
| | | |
| | | // .. we expect to receive entries or err after that |
| | | } |
| | | |
| | | /** |
| | | * Initializes the domain's backend with received entries. |
| | | * @param initializeMessage The message that initiated the import. |
| | | * @exception DirectoryException Thrown when an error occurs. |
| | | */ |
| | | void initialize(InitializeTargetMsg initializeMessage) |
| | | throws DirectoryException |
| | | { |
| | | DirectoryException de = null; |
| | | |
| | | Message msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_START.get( |
| | | Short.toString(serverID), |
| | | serviceID, |
| | | Long.toString(initializeMessage.getRequestorID())); |
| | | logError(msg); |
| | | |
| | | // Go into full update status |
| | | setNewStatus(StatusMachineEvent.TO_FULL_UPDATE_STATUS_EVENT); |
| | | |
| | | if (initializeMessage.getRequestorID() == serverID) |
| | | { |
| | | // The import responds to a request we did so the IEContext |
| | | // is already acquired |
| | | } |
| | | else |
| | | { |
| | | acquireIEContext(true); |
| | | } |
| | | |
| | | ieContext.importSource = initializeMessage.getsenderID(); |
| | | ieContext.entryLeftCount = initializeMessage.getEntryCount(); |
| | | ieContext.setCounters( |
| | | initializeMessage.getEntryCount(), |
| | | initializeMessage.getEntryCount()); |
| | | ieContext.ldifImportInputStream = new ReplInputStream(this); |
| | | |
| | | try |
| | | { |
| | | importBackend(new ReplInputStream(this)); |
| | | broker.reStart(); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | | de = e; |
| | | } |
| | | finally |
| | | { |
| | | if ((ieContext != null) && (ieContext.exception != null)) |
| | | de = ieContext.exception; |
| | | |
| | | // Update the task that initiated the import |
| | | if ((ieContext != null ) && (ieContext.initializeTask != null)) |
| | | { |
| | | ((InitializeTask)ieContext.initializeTask). |
| | | updateTaskCompletionState(de); |
| | | } |
| | | releaseIEContext(); |
| | | } |
| | | |
| | | // Sends up the root error. |
| | | if (de != null) |
| | | { |
| | | throw de; |
| | | } |
| | | |
| | | msg = NOTE_FULL_UPDATE_ENGAGED_FROM_REMOTE_END.get( |
| | | Short.toString(serverID), |
| | | serviceID, |
| | | Long.toString(initializeMessage.getRequestorID())); |
| | | logError(msg); |
| | | } |
| | | |
| | | /** |
| | | * Sets the status to a new value depending of the passed status machine |
| | | * event. |
| | | * @param event The event that may make the status be changed |
| | | */ |
| | | private void setNewStatus(StatusMachineEvent event) |
| | | { |
| | | ServerStatus newStatus = |
| | | StatusMachine.computeNewStatus(status, event); |
| | | |
| | | if (newStatus == ServerStatus.INVALID_STATUS) |
| | | { |
| | | Message msg = ERR_DS_CANNOT_CHANGE_STATUS.get(serviceID, |
| | | Short.toString(serverID), status.toString(), event.toString()); |
| | | logError(msg); |
| | | return; |
| | | } |
| | | |
| | | if (newStatus != status) |
| | | { |
| | | // Store new status |
| | | status = newStatus; |
| | | lastStatusChangeDate = new Date(); |
| | | // Reset assured monitoring counters (they are valid for a session with |
| | | // the same status/RS) |
| | | resetAssuredMonitoringCounters(); |
| | | |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo("Replication domain " + serviceID + |
| | | " new status is: " + status); |
| | | |
| | | // Perform whatever actions are needed to apply properties for being |
| | | // compliant with new status |
| | | updateDomainForNewStatus(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Returns a boolean indicating if an import or export is currently |
| | | * processed. |
| | | * |
| | | * @return The status |
| | | */ |
| | | public boolean ieRunning() |
| | | { |
| | | return (ieContext != null); |
| | | } |
| | | |
| | | /** |
| | | * Verifies that the given string represents a valid source |
| | | * from which this server can be initialized. |
| | | * @param sourceString The string representing the source |
| | | * @return The source as a short value |
| | | * @throws DirectoryException if the string is not valid |
| | | */ |
| | | short decodeSource(String sourceString) |
| | | throws DirectoryException |
| | | { |
| | | short source = 0; |
| | | Throwable cause = null; |
| | | try |
| | | { |
| | | source = Integer.decode(sourceString).shortValue(); |
| | | if ((source >= -1) && (source != serverID)) |
| | | { |
| | | // TODO Verifies serverID is in the domain |
| | | // We should check here that this is a server implied |
| | | // in the current domain. |
| | | return source; |
| | | } |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | cause = e; |
| | | } |
| | | |
| | | ResultCode resultCode = ResultCode.OTHER; |
| | | Message message = ERR_INVALID_IMPORT_SOURCE.get(); |
| | | if (cause != null) |
| | | { |
| | | throw new DirectoryException( |
| | | resultCode, message, cause); |
| | | } |
| | | else |
| | | { |
| | | throw new DirectoryException( |
| | | resultCode, message); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Reset the generationId of this domain in the whole topology. |
| | | * A message is sent to the Replication Servers for them to reset |
| | | * their change dbs. |
| | | * |
| | | * @param generationIdNewValue The new value of the generation Id. |
| | | * @throws DirectoryException when an error occurs |
| | | */ |
| | | void resetGenerationId(Long generationIdNewValue) |
| | | throws DirectoryException |
| | | { |
| | | if (debugEnabled()) |
| | | TRACER.debugInfo( |
| | | "Server id " + serverID + " and domain " + serviceID |
| | | + "resetGenerationId" + generationIdNewValue); |
| | | |
| | | if (!isConnected()) |
| | | { |
| | | ResultCode resultCode = ResultCode.OTHER; |
| | | Message message = ERR_RESET_GENERATION_CONN_ERR_ID.get( |
| | | serviceID); |
| | | throw new DirectoryException( |
| | | resultCode, message); |
| | | } |
| | | |
| | | ResetGenerationIdMsg genIdMessage = null; |
| | | |
| | | if (generationIdNewValue == null) |
| | | { |
| | | genIdMessage = new ResetGenerationIdMsg(this.getGenerationID()); |
| | | } |
| | | else |
| | | { |
| | | genIdMessage = new ResetGenerationIdMsg(generationIdNewValue); |
| | | } |
| | | broker.publish(genIdMessage); |
| | | } |
| | | |
| | | |
| | | /* |
| | | ******** End of The total Update code ********* |
| | | */ |
| | | |
| | | /* |
| | | ******* Start of Monitoring Code ********** |
| | | */ |
| | | |
| | | /** |
| | | * Get the maximum receive window size. |
| | | * |
| | | * @return The maximum receive window size. |
| | | */ |
| | | int getMaxRcvWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getMaxRcvWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the current receive window size. |
| | | * |
| | | * @return The current receive window size. |
| | | */ |
| | | int getCurrentRcvWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getCurrentRcvWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the maximum send window size. |
| | | * |
| | | * @return The maximum send window size. |
| | | */ |
| | | int getMaxSendWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getMaxSendWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the current send window size. |
| | | * |
| | | * @return The current send window size. |
| | | */ |
| | | int getCurrentSendWindow() |
| | | { |
| | | if (broker != null) |
| | | return broker.getCurrentSendWindow(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Get the number of times the replication connection was lost. |
| | | * @return The number of times the replication connection was lost. |
| | | */ |
| | | int getNumLostConnections() |
| | | { |
| | | if (broker != null) |
| | | return broker.getNumLostConnections(); |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Determine whether the connection to the replication server is encrypted. |
| | | * @return true if the connection is encrypted, false otherwise. |
| | | */ |
| | | boolean isSessionEncrypted() |
| | | { |
| | | if (broker != null) |
| | | return broker.isSessionEncrypted(); |
| | | else |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * This method is called when the ReplicationDomain has completed the |
| | | * processing of a received update synchronously. |
| | | * In such cases the processUpdateDone () is called and the state |
| | | * is updated automatically. |
| | | * |
| | | * @param msg The UpdateMessage that was processed. |
| | | */ |
| | | void processUpdateDoneSynchronous(UpdateMsg msg) |
| | | { |
| | | // Warning: in synchronous mode, no way to tell the replay of an update went |
| | | // wrong Just put null in processUpdateDone so that if assured replication |
| | | // is used the ack is sent without error at replay flag. |
| | | processUpdateDone(msg, null); |
| | | state.update(msg.getChangeNumber()); |
| | | } |
| | | |
| | | /** |
| | | * Check if the domain is connected to a ReplicationServer. |
| | | * |
| | | * @return true if the server is connected, false if not. |
| | | */ |
| | | public boolean isConnected() |
| | | { |
| | | if (broker != null) |
| | | return broker.isConnected(); |
| | | else |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Get the name of the replicationServer to which this domain is currently |
| | | * connected. |
| | | * |
| | | * @return the name of the replicationServer to which this domain |
| | | * is currently connected. |
| | | */ |
| | | public String getReplicationServer() |
| | | { |
| | | if (broker != null) |
| | | return broker.getReplicationServer(); |
| | | else |
| | | return "Not connected"; |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode. |
| | | * @return The number of updates sent in assured safe read mode. |
| | | */ |
| | | public int getAssuredSrSentUpdates() |
| | | { |
| | | return assuredSrSentUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode that have been |
| | | * acknowledged without errors. |
| | | * @return The number of updates sent in assured safe read mode that have been |
| | | * acknowledged without errors. |
| | | */ |
| | | public int getAssuredSrAcknowledgedUpdates() |
| | | { |
| | | return assuredSrAcknowledgedUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode that have not |
| | | * been acknowledged. |
| | | * @return The number of updates sent in assured safe read mode that have not |
| | | * been acknowledged. |
| | | */ |
| | | public int getAssuredSrNotAcknowledgedUpdates() |
| | | { |
| | | return assuredSrNotAcknowledgedUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode that have not |
| | | * been acknowledged due to timeout error. |
| | | * @return The number of updates sent in assured safe read mode that have not |
| | | * been acknowledged due to timeout error. |
| | | */ |
| | | public int getAssuredSrTimeoutUpdates() |
| | | { |
| | | return assuredSrTimeoutUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode that have not |
| | | * been acknowledged due to wrong status error. |
| | | * @return The number of updates sent in assured safe read mode that have not |
| | | * been acknowledged due to wrong status error. |
| | | */ |
| | | public int getAssuredSrWrongStatusUpdates() |
| | | { |
| | | return assuredSrWrongStatusUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode that have not |
| | | * been acknowledged due to replay error. |
| | | * @return The number of updates sent in assured safe read mode that have not |
| | | * been acknowledged due to replay error. |
| | | */ |
| | | public int getAssuredSrReplayErrorUpdates() |
| | | { |
| | | return assuredSrReplayErrorUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe read mode that have not |
| | | * been acknowledged per server. |
| | | * @return The number of updates sent in assured safe read mode that have not |
| | | * been acknowledged per server. |
| | | */ |
| | | public Map<Short, Integer> getAssuredSrServerNotAcknowledgedUpdates() |
| | | { |
| | | return assuredSrServerNotAcknowledgedUpdates; |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe data mode. |
| | | * @return The number of updates sent in assured safe data mode. |
| | | */ |
| | | public int getAssuredSdSentUpdates() |
| | | { |
| | | return assuredSdSentUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe data mode that have been |
| | | * acknowledged without errors. |
| | | * @return The number of updates sent in assured safe data mode that have been |
| | | * acknowledged without errors. |
| | | */ |
| | | public int getAssuredSdAcknowledgedUpdates() |
| | | { |
| | | return assuredSdAcknowledgedUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe data mode that have not |
| | | * been acknowledged due to timeout error. |
| | | * @return The number of updates sent in assured safe data mode that have not |
| | | * been acknowledged due to timeout error. |
| | | */ |
| | | public int getAssuredSdTimeoutUpdates() |
| | | { |
| | | return assuredSdTimeoutUpdates.get(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the number of updates sent in assured safe data mode that have not |
| | | * been acknowledged due to timeout error per server. |
| | | * @return The number of updates sent in assured safe data mode that have not |
| | | * been acknowledged due to timeout error per server. |
| | | */ |
| | | public Map<Short, Integer> getAssuredSdServerTimeoutUpdates() |
| | | { |
| | | return assuredSdServerTimeoutUpdates; |
| | | } |
| | | |
| | | /** |
| | | * Gets the date of the last status change. |
| | | * @return The date of the last status change. |
| | | */ |
| | | public Date getLastStatusChangeDate() |
| | | { |
| | | return lastStatusChangeDate; |
| | | } |
| | | |
| | | /** |
| | | * Resets the values of the monitoring counters for assured replication. |
| | | */ |
| | | private void resetAssuredMonitoringCounters() |
| | | { |
| | | assuredSrSentUpdates = new AtomicInteger(0); |
| | | assuredSrAcknowledgedUpdates = new AtomicInteger(0); |
| | | assuredSrNotAcknowledgedUpdates = new AtomicInteger(0); |
| | | assuredSrTimeoutUpdates = new AtomicInteger(0); |
| | | assuredSrWrongStatusUpdates = new AtomicInteger(0); |
| | | assuredSrReplayErrorUpdates = new AtomicInteger(0); |
| | | assuredSrServerNotAcknowledgedUpdates = new HashMap<Short,Integer>(); |
| | | assuredSdSentUpdates = new AtomicInteger(0); |
| | | assuredSdAcknowledgedUpdates = new AtomicInteger(0); |
| | | assuredSdTimeoutUpdates = new AtomicInteger(0); |
| | | assuredSdServerTimeoutUpdates = new HashMap<Short,Integer>(); |
| | | } |
| | | |
| | | /* |
| | | ********** End of Monitoring Code ************** |
| | | */ |
| | | |
| | | /** |
| | | * Start the publish mechanism of the Replication Service. |
| | | * After this method has been called, the publish service can be used |
| | | * by calling the {@link #publish(UpdateMsg)} method. |
| | | * |
| | | * @param replicationServers The replication servers that should be used. |
| | | * @param window The window size of this replication domain. |
| | | * @param heartbeatInterval The heartbeatInterval that should be used |
| | | * to check the availability of the replication |
| | | * servers. |
| | | * |
| | | * @throws ConfigException If the DirectoryServer configuration was |
| | | * incorrect. |
| | | */ |
| | | public void startPublishService( |
| | | Collection<String> replicationServers, int window, |
| | | long heartbeatInterval) throws ConfigException |
| | | { |
| | | /* |
| | | * create the broker object used to publish and receive changes |
| | | */ |
| | | broker = new ReplicationBroker( |
| | | this, state, serviceID, |
| | | serverID, window, |
| | | getGenerationID(), |
| | | heartbeatInterval, |
| | | new ReplSessionSecurity(), |
| | | getGroupId()); |
| | | |
| | | broker.start(replicationServers); |
| | | |
| | | /* |
| | | * Create a replication monitor object responsible for publishing |
| | | * monitoring information below cn=monitor. |
| | | */ |
| | | monitor = new ReplicationMonitor(this); |
| | | |
| | | DirectoryServer.registerMonitorProvider(monitor); |
| | | } |
| | | |
| | | /** |
| | | * Starts the receiver side of the Replication Service. |
| | | * <p> |
| | | * After this method has been called, the Replication Service will start |
| | | * calling the {@link #processUpdate(UpdateMsg)}. |
| | | */ |
| | | public void startListenService() |
| | | { |
| | | // Create the listener thread |
| | | listenerThread = new ListenerThread(this); |
| | | listenerThread.start(); |
| | | } |
| | | |
| | | /** |
| | | * Temporarily disable the Replication Service. |
| | | * The Replication Service can be enabled again using |
| | | * {@link #enableService()}. |
| | | * <p> |
| | | * It can be useful to disable the Replication Service when the |
| | | * repository where the replicated information is stored becomes |
| | | * temporarily unavailable and replicated updates can therefore not |
| | | * be replayed during a while. |
| | | */ |
| | | public void disableService() |
| | | { |
| | | // Stop the listener thread |
| | | if (listenerThread != null) |
| | | { |
| | | listenerThread.shutdown(); |
| | | } |
| | | |
| | | if (broker != null) |
| | | { |
| | | broker.stop(); |
| | | } |
| | | |
| | | // Wait for the listener thread to stop |
| | | if (listenerThread != null) |
| | | listenerThread.waitForShutdown(); |
| | | } |
| | | |
| | | /** |
| | | * Restart the Replication service after a {@link #disableService()}. |
| | | * <p> |
| | | * The Replication Service will restart from the point indicated by the |
| | | * {@link ServerState} that was given as a parameter to the |
| | | * {@link #startPublishService(Collection, ServerState, int, long)} |
| | | * at startup time. |
| | | * If some data have changed in the repository during the period of time when |
| | | * the Replication Service was disabled, this {@link ServerState} should |
| | | * therefore be updated by the Replication Domain subclass before calling |
| | | * this method. |
| | | */ |
| | | public void enableService() |
| | | { |
| | | broker.start(); |
| | | |
| | | // Create the listener thread |
| | | listenerThread = new ListenerThread(this); |
| | | listenerThread.start(); |
| | | } |
| | | |
| | | /** |
| | | * Definitively stops the Replication Service. |
| | | */ |
| | | public void stopDomain() |
| | | { |
| | | DirectoryServer.deregisterMonitorProvider(monitor.getMonitorInstanceName()); |
| | | |
| | | disableService(); |
| | | domains.remove(serviceID); |
| | | } |
| | | |
| | | /** |
| | | * Change the ReplicationDomain parameters. |
| | | * |
| | | * @param replicationServers The new list of Replication Servers that this |
| | | * domain should now use. |
| | | * @param windowSize The window size that this domain should use. |
| | | * @param heartbeatInterval The heartbeatInterval that this domain should |
| | | * use. |
| | | */ |
| | | public void changeConfig( |
| | | Collection<String> replicationServers, |
| | | int windowSize, |
| | | long heartbeatInterval) |
| | | { |
| | | if (broker != null) |
| | | { |
| | | if (broker.changeConfig( |
| | | replicationServers, windowSize, heartbeatInterval)) |
| | | { |
| | | disableService(); |
| | | enableService(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This method should trigger an export of the replicated data. |
| | | * to the provided outputStream. |
| | | * When finished the outputStream should be flushed and closed. |
| | | * |
| | | * @param output The OutputStream where the export should |
| | | * be produced. |
| | | * @throws DirectoryException When needed. |
| | | */ |
| | | abstract protected void exportBackend(OutputStream output) |
| | | throws DirectoryException; |
| | | |
| | | /** |
| | | * This method should trigger an import of the replicated data. |
| | | * |
| | | * @param input The InputStream from which |
| | | * the import should be reading entries. |
| | | * |
| | | * @throws DirectoryException When needed. |
| | | */ |
| | | abstract protected void importBackend(InputStream input) |
| | | throws DirectoryException; |
| | | |
| | | /** |
| | | * This method should return the total number of objects in the |
| | | * replicated domain. |
| | | * This count will be used for reporting. |
| | | * |
| | | * @throws DirectoryException when needed. |
| | | * |
| | | * @return The number of objects in the replication domain. |
| | | */ |
| | | public abstract long countEntries() throws DirectoryException; |
| | | |
| | | /** |
| | | * This method should handle the processing of {@link UpdateMsg} receive |
| | | * from remote replication entities. |
| | | * <p> |
| | | * This method will be called by a single thread and should therefore |
| | | * should not be blocking. |
| | | * |
| | | * @param updateMsg The {@link UpdateMsg} that was received. |
| | | * |
| | | * @return A boolean indicating if the processing is completed at return |
| | | * time. |
| | | * If <code> true </code> is returned, no further |
| | | * processing is necessary. |
| | | * If <code> false </code> is returned, the subclass should |
| | | * call the method |
| | | * {@link #processUpdateDone(UpdateMsg)} |
| | | * and update the ServerState |
| | | * When this processing is complete. |
| | | * |
| | | */ |
| | | public abstract boolean processUpdate(UpdateMsg updateMsg); |
| | | |
| | | /** |
| | | * This method must be called after each call to |
| | | * {@link #processUpdate(UpdateMsg)} when the processing of the update is |
| | | * completed. |
| | | * <p> |
| | | * It is useful for implementation needing to process the update in an |
| | | * asynchronous way or using several threads, but must be called even |
| | | * by implementation doing it in a synchronous, single-threaded way. |
| | | * |
| | | * @param msg The UpdateMsg whose processing was completed. |
| | | * @param replayErrorMsg if not null, this means an error occurred during the |
| | | * replay of this update, and this is the matching human readable message |
| | | * describing the problem. |
| | | */ |
| | | public void processUpdateDone(UpdateMsg msg, String replayErrorMsg) |
| | | { |
| | | broker.updateWindowAfterReplay(); |
| | | |
| | | // Send an ack if it was requested and the group id is the same of the RS |
| | | // one. Only Safe Read mode makes sense in DS for returning an ack. |
| | | byte rsGroupId = broker.getRsGroupId(); |
| | | if (msg.isAssured()) |
| | | { |
| | | // Assured feature is supported starting from replication protocol V2 |
| | | if (broker.getProtocolVersion() >= |
| | | ProtocolVersion.REPLICATION_PROTOCOL_V2) |
| | | { |
| | | AssuredMode msgAssuredMode = msg.getAssuredMode(); |
| | | if (msgAssuredMode == AssuredMode.SAFE_READ_MODE) |
| | | { |
| | | if (rsGroupId == groupId) |
| | | { |
| | | // Send the ack |
| | | AckMsg ackMsg = new AckMsg(msg.getChangeNumber()); |
| | | if (replayErrorMsg != null) |
| | | { |
| | | // Mark the error in the ack |
| | | // -> replay error occured |
| | | ackMsg.setHasReplayError(true); |
| | | // -> replay error occured in our server |
| | | List<Short> idList = new ArrayList<Short>(); |
| | | idList.add(serverID); |
| | | ackMsg.setFailedServers(idList); |
| | | } |
| | | broker.publish(ackMsg); |
| | | } |
| | | } else if (assuredMode != AssuredMode.SAFE_DATA_MODE) |
| | | { |
| | | Message errorMsg = ERR_DS_UNKNOWN_ASSURED_MODE.get( |
| | | Short.toString(serverID), msgAssuredMode.toString(), serviceID, |
| | | msg.toString()); |
| | | logError(errorMsg); |
| | | } else |
| | | { |
| | | // In safe data mode assured update that comes up to a DS requires no |
| | | // ack from a destinator DS. Safe data mode is based on RS acks only |
| | | } |
| | | } |
| | | } |
| | | |
| | | incProcessedUpdates(); |
| | | } |
| | | |
| | | /** |
| | | * Publish an {@link UpdateMsg} to the Replication Service. |
| | | * <p> |
| | | * The Replication Service will handle the delivery of this {@link UpdateMsg} |
| | | * to all the participants of this Replication Domain. |
| | | * These members will be receive this {@link UpdateMsg} through a call |
| | | * of the {@link #processUpdate(UpdateMsg)} message. |
| | | * |
| | | * @param msg The UpdateMsg that should be pushed. |
| | | * @throws TimeoutException When assured replication is enabled and the |
| | | * configured timeout occurs when blocked waiting for the ack. |
| | | */ |
| | | public void publish(UpdateMsg msg) throws TimeoutException |
| | | { |
| | | byte rsGroupId = broker.getRsGroupId(); |
| | | |
| | | // If assured configured, set message accordingly to request an ack in the |
| | | // right assured mode. |
| | | // No ack requested for a RS with a different group id. Assured replication |
| | | // suported for the same locality, i.e: a topology working in the same |
| | | // geographical location). If we are connected to a RS which is not in our |
| | | // locality, no need to ask for an ack. |
| | | if ( assured && ( rsGroupId == groupId ) ) |
| | | { |
| | | msg.setAssured(true); |
| | | msg.setAssuredMode(assuredMode); |
| | | if (assuredMode == AssuredMode.SAFE_DATA_MODE) |
| | | msg.setSafeDataLevel(assuredSdLevel); |
| | | |
| | | // Add the assured message to the list of update that are |
| | | // waiting for acks |
| | | synchronized (waitingAckMsgs) |
| | | { |
| | | waitingAckMsgs.put(msg.getChangeNumber(), msg); |
| | | } |
| | | } |
| | | |
| | | // Publish the update |
| | | broker.publish(msg); |
| | | state.update(msg.getChangeNumber()); |
| | | numSentUpdates.incrementAndGet(); |
| | | |
| | | // If assured mode configured, wait for acknowledgement for the just sent |
| | | // message |
| | | if ( assured && ( rsGroupId == groupId ) ) |
| | | { |
| | | // Increment assured replication monitoring counters |
| | | switch(assuredMode) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | assuredSrSentUpdates.incrementAndGet(); |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | assuredSdSentUpdates.incrementAndGet(); |
| | | break; |
| | | default: |
| | | // Should no happen |
| | | } |
| | | |
| | | // Now wait for ack matching the sent assured update |
| | | waitForAck(msg); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Publish informations to the Replication Service (not assured mode). |
| | | * |
| | | * @param msg The byte array containing the informations that should |
| | | * be sent to the remote entities. |
| | | */ |
| | | public void publish(byte[] msg) |
| | | { |
| | | UpdateMsg update = new UpdateMsg(generator.newChangeNumber(), msg); |
| | | try |
| | | { |
| | | publish(update); |
| | | } catch (TimeoutException e) |
| | | { |
| | | // Should never happen as assured mode not requested |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * This method should return the generationID to use for this |
| | | * ReplicationDomain. |
| | | * This method can be called at any time after the ReplicationDomain |
| | | * has been started. |
| | | * |
| | | * @return The GenerationID. |
| | | */ |
| | | public abstract long getGenerationID(); |
| | | |
| | | /** |
| | | * Subclasses should use this method to add additional monitoring |
| | | * information in the ReplicationDomain. |
| | | * |
| | | * @return Additional monitoring attributes that will be added in the |
| | | * ReplicationDomain monitoring entry. |
| | | */ |
| | | public Collection<Attribute> getAdditionalMonitoring() |
| | | { |
| | | return new ArrayList<Attribute>(); |
| | | } |
| | | |
| | | /** |
| | | * Returns a boolean indicating if a total update import is currently |
| | | * in Progress. |
| | | * |
| | | * @return A boolean indicating if a total update import is currently |
| | | * in Progress. |
| | | */ |
| | | boolean importInProgress() |
| | | { |
| | | if (ieContext == null) |
| | | return false; |
| | | else |
| | | return ieContext.importInProgress; |
| | | } |
| | | |
| | | /** |
| | | * Returns a boolean indicating if a total update export is currently |
| | | * in Progress. |
| | | * |
| | | * @return A boolean indicating if a total update export is currently |
| | | * in Progress. |
| | | */ |
| | | boolean exportInProgress() |
| | | { |
| | | if (ieContext == null) |
| | | return false; |
| | | else |
| | | return !ieContext.importInProgress; |
| | | } |
| | | |
| | | /** |
| | | * Returns the number of entries still to be processed when a total update |
| | | * is in progress. |
| | | * |
| | | * @return The number of entries still to be processed when a total update |
| | | * is in progress. |
| | | */ |
| | | long getLeftEntryCount() |
| | | { |
| | | if (ieContext != null) |
| | | return ieContext.entryLeftCount; |
| | | else |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Returns the total number of entries to be processed when a total update |
| | | * is in progress. |
| | | * |
| | | * @return The total number of entries to be processed when a total update |
| | | * is in progress. |
| | | */ |
| | | long getTotalEntryCount() |
| | | { |
| | | if (ieContext != null) |
| | | return ieContext.entryCount; |
| | | else |
| | | return 0; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | | import java.util.Collection; |
| | | |
| | | import java.util.ArrayList; |
| | | |
| | | import java.util.Map; |
| | | import org.opends.server.admin.std.server.MonitorProviderCfg; |
| | | import org.opends.server.api.MonitorProvider; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeBuilder; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.Attributes; |
| | | |
| | | /** |
| | | * Class used to generate monitoring information for the replication. |
| | | */ |
| | | public class ReplicationMonitor extends MonitorProvider<MonitorProviderCfg> |
| | | { |
| | | private ReplicationDomain domain; // the replication plugin |
| | | |
| | | /** |
| | | * Create a new replication monitor. |
| | | * @param domain the plugin which created the monitor |
| | | */ |
| | | public ReplicationMonitor(ReplicationDomain domain) |
| | | { |
| | | super("Replication monitor " + domain.getServiceID() |
| | | + " " + domain.getServerId()); |
| | | this.domain = domain; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public void initializeMonitorProvider(MonitorProviderCfg configuration) |
| | | { |
| | | // no implementation needed. |
| | | } |
| | | |
| | | /** |
| | | * 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 "Replication Domain " + domain.getServiceID() |
| | | + " " + domain.getServerId(); |
| | | } |
| | | |
| | | /** |
| | | * 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>(); |
| | | |
| | | /* get the base dn */ |
| | | Attribute attr = Attributes.create("base-dn", domain.getServiceID()); |
| | | attributes.add(attr); |
| | | |
| | | /* get the base dn */ |
| | | attr = Attributes.create("connected-to", domain |
| | | .getReplicationServer()); |
| | | attributes.add(attr); |
| | | |
| | | /* get number of lost connections */ |
| | | addMonitorData(attributes, "lost-connections", |
| | | domain.getNumLostConnections()); |
| | | |
| | | /* get number of received updates */ |
| | | addMonitorData(attributes, "received-updates", domain.getNumRcvdUpdates()); |
| | | |
| | | /* get number of updates sent */ |
| | | addMonitorData(attributes, "sent-updates", domain.getNumSentUpdates()); |
| | | |
| | | /* get number of changes replayed */ |
| | | addMonitorData(attributes, "replayed-updates", |
| | | domain.getNumProcessedUpdates()); |
| | | |
| | | /* get server-id */ |
| | | addMonitorData(attributes, "server-id", |
| | | domain.getServerId()); |
| | | |
| | | /* get window information */ |
| | | addMonitorData(attributes, "max-rcv-window", domain.getMaxRcvWindow()); |
| | | addMonitorData(attributes, "current-rcv-window", |
| | | domain.getCurrentRcvWindow()); |
| | | addMonitorData(attributes, "max-send-window", |
| | | domain.getMaxSendWindow()); |
| | | addMonitorData(attributes, "current-send-window", |
| | | domain.getCurrentSendWindow()); |
| | | |
| | | /* get the Server State */ |
| | | final String ATTR_SERVER_STATE = "server-state"; |
| | | AttributeType type = |
| | | DirectoryServer.getDefaultAttributeType(ATTR_SERVER_STATE); |
| | | AttributeBuilder builder = new AttributeBuilder(type, ATTR_SERVER_STATE); |
| | | for (String str : domain.getServerState().toStringSet()) |
| | | { |
| | | builder.add(new AttributeValue(type,str)); |
| | | } |
| | | attributes.add(builder.toAttribute()); |
| | | |
| | | attributes.add(Attributes.create("ssl-encryption", |
| | | String.valueOf(domain.isSessionEncrypted()))); |
| | | |
| | | attributes.add(Attributes.create("generation-id", |
| | | String.valueOf(domain.getGenerationID()))); |
| | | |
| | | /* |
| | | * Add import/export monitoring attribute |
| | | */ |
| | | if (domain.importInProgress()) |
| | | { |
| | | addMonitorData(attributes, "total-update", "import"); |
| | | addMonitorData( |
| | | attributes, "total-update-entry-count", domain.getTotalEntryCount()); |
| | | addMonitorData( |
| | | attributes, "total-update-entry-left", domain.getLeftEntryCount()); |
| | | } |
| | | if (domain.exportInProgress()) |
| | | { |
| | | addMonitorData(attributes, "total-update", "export"); |
| | | addMonitorData( |
| | | attributes, "total-update-entry-count", domain.getTotalEntryCount()); |
| | | addMonitorData( |
| | | attributes, "total-update-entry-left", domain.getLeftEntryCount()); |
| | | } |
| | | |
| | | |
| | | /* Add the concrete Domain attributes */ |
| | | Collection<Attribute> additionalMonitoring = |
| | | domain.getAdditionalMonitoring(); |
| | | attributes.addAll(additionalMonitoring); |
| | | |
| | | /* |
| | | * Add assured replication related monitoring fields |
| | | * (see domain.getXXX() method comment for field meaning) |
| | | */ |
| | | |
| | | addMonitorData(attributes, "assured-sr-sent-updates", |
| | | domain.getAssuredSrSentUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sr-acknowledged-updates", |
| | | domain.getAssuredSrAcknowledgedUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sr-not-acknowledged-updates", |
| | | domain.getAssuredSrNotAcknowledgedUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sr-timeout-updates", |
| | | domain.getAssuredSrTimeoutUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sr-wrong-status-updates", |
| | | domain.getAssuredSrWrongStatusUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sr-replay-error-updates", |
| | | domain.getAssuredSrReplayErrorUpdates()); |
| | | |
| | | final String ATTR_ASS_SR_SRV = "assured-sr-server-not-acknowledged-updates"; |
| | | type = DirectoryServer.getDefaultAttributeType(ATTR_ASS_SR_SRV); |
| | | builder = new AttributeBuilder(type, ATTR_ASS_SR_SRV); |
| | | Map<Short, Integer> srSrvNotAckUps = |
| | | domain.getAssuredSrServerNotAcknowledgedUpdates(); |
| | | if (srSrvNotAckUps.size() > 0) |
| | | { |
| | | for (Short serverId : srSrvNotAckUps.keySet()) |
| | | { |
| | | String str = serverId + ":" + srSrvNotAckUps.get(serverId); |
| | | builder.add(new AttributeValue(type, str)); |
| | | } |
| | | attributes.add(builder.toAttribute()); |
| | | } |
| | | |
| | | addMonitorData(attributes, "assured-sd-sent-updates", |
| | | domain.getAssuredSdSentUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sd-acknowledged-updates", |
| | | domain.getAssuredSdAcknowledgedUpdates()); |
| | | |
| | | addMonitorData(attributes, "assured-sd-timeout-updates", |
| | | domain.getAssuredSdTimeoutUpdates()); |
| | | |
| | | final String ATTR_ASS_SD_SRV = "assured-sd-server-timeout-updates"; |
| | | type = DirectoryServer.getDefaultAttributeType(ATTR_ASS_SD_SRV); |
| | | builder = new AttributeBuilder(type, ATTR_ASS_SD_SRV); |
| | | Map<Short, Integer> sdSrvTimUps = |
| | | domain.getAssuredSdServerTimeoutUpdates(); |
| | | if (sdSrvTimUps.size() > 0) |
| | | { |
| | | for (Short serverId : sdSrvTimUps.keySet()) |
| | | { |
| | | String str = serverId + ":" + sdSrvTimUps.get(serverId); |
| | | builder.add(new AttributeValue(type, str)); |
| | | } |
| | | attributes.add(builder.toAttribute()); |
| | | } |
| | | |
| | | /* |
| | | * Status related monitoring fields |
| | | */ |
| | | |
| | | addMonitorData(attributes, "last-status-change-date", |
| | | domain.getLastStatusChangeDate().toString()); |
| | | |
| | | addMonitorData(attributes, "status", domain.getStatus().toString()); |
| | | |
| | | return attributes; |
| | | |
| | | } |
| | | |
| | | /** |
| | | * Add an attribute with an integer value to the list of monitoring |
| | | * attributes. |
| | | * |
| | | * @param attributes the list of monitoring attributes |
| | | * @param name the name of the attribute to add. |
| | | * @param value The integer value of he attribute to add. |
| | | */ |
| | | public static void addMonitorData( |
| | | ArrayList<Attribute> attributes, |
| | | String name, |
| | | int value) |
| | | { |
| | | AttributeType type = DirectoryServer.getDefaultAttributeType(name); |
| | | attributes.add(Attributes.create(type, new AttributeValue(type, |
| | | String.valueOf(value)))); |
| | | } |
| | | |
| | | /** |
| | | * Add an attribute with an integer value to the list of monitoring |
| | | * attributes. |
| | | * |
| | | * @param attributes the list of monitoring attributes |
| | | * @param name the name of the attribute to add. |
| | | * @param value The integer value of he attribute to add. |
| | | */ |
| | | public static void addMonitorData( |
| | | ArrayList<Attribute> attributes, |
| | | String name, |
| | | long value) |
| | | { |
| | | AttributeType type = DirectoryServer.getDefaultAttributeType(name); |
| | | attributes.add(Attributes.create(type, new AttributeValue(type, |
| | | String.valueOf(value)))); |
| | | } |
| | | |
| | | /** |
| | | * Add an attribute with an integer value to the list of monitoring |
| | | * attributes. |
| | | * |
| | | * @param attributes the list of monitoring attributes |
| | | * @param name the name of the attribute to add. |
| | | * @param value The String value of he attribute to add. |
| | | */ |
| | | public static void addMonitorData( |
| | | ArrayList<Attribute> attributes, |
| | | String name, |
| | | String value) |
| | | { |
| | | AttributeType type = DirectoryServer.getDefaultAttributeType(name); |
| | | attributes.add(Attributes.create(type, new AttributeValue(type, value))); |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | | } |
| | | } |
| File was renamed from opends/src/server/org/opends/server/tasks/SetGenerationIdTask.java |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.tasks; |
| | | package org.opends.server.replication.service; |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | | import static org.opends.server.core.DirectoryServer.getAttributeType; |
| | | |
| | | import org.opends.server.tasks.TaskUtils; |
| | | |
| | | |
| | | import java.util.List; |
| | | |
| | | import org.opends.messages.MessageBuilder; |
| | |
| | | import org.opends.server.backends.task.TaskState; |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.ResultCode; |
| | |
| | | * The tracer object for the debug logger. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | boolean isCompressed = false; |
| | | boolean isEncrypted = false; |
| | | boolean skipSchemaValidation = false; |
| | | String domainString = null; |
| | | ReplicationDomain domain = null; |
| | | TaskState initState; |
| | | Long generationId = null; |
| | | private String domainString = null; |
| | | private ReplicationDomain domain = null; |
| | | private Long generationId = null; |
| | | |
| | | private static final void debugInfo(String s) |
| | | { |
| | |
| | | |
| | | attrList = taskEntry.getAttribute(typeDomainBase); |
| | | domainString = TaskUtils.getSingleValueString(attrList); |
| | | DN domainDN = DN.nullDN(); |
| | | |
| | | try |
| | | { |
| | | domainDN = DN.decode(domainString); |
| | | domain = ReplicationDomain.retrievesReplicationDomain(domainString); |
| | | } |
| | | catch(Exception e) |
| | | catch(DirectoryException e) |
| | | { |
| | | MessageBuilder mb = new MessageBuilder(); |
| | | mb.append(TaskMessages.ERR_TASK_INITIALIZE_INVALID_DN.get()); |
| | | mb.append(e.getMessage()); |
| | | throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, |
| | | mb.toMessage()); |
| | | throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, e); |
| | | } |
| | | |
| | | domain = ReplicationDomain.retrievesReplicationDomain(domainDN); |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | protected TaskState runTask() |
| | | { |
| | | debugInfo("setGenerationIdTask is starting on domain%s" + |
| | | domain.getBaseDN()); |
| | | domain.getServiceID()); |
| | | |
| | | try |
| | | { |
| File was renamed from opends/src/server/org/opends/server/replication/server/AckMessageListComparator.java |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Copyright 2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.server; |
| | | |
| | | import java.util.Comparator; |
| | | |
| | | |
| | | /** |
| | | * This comparator is used to build TreeSet of AckMessageLists. |
| | | * This package contains the generic of the Multi-Master |
| | | * replication code that works on the Directory Server side. |
| | | * <br> |
| | | * Developers planning to use the Replication Service should |
| | | * subClass the <A> HREF="ReplicationDomain.html"><B>ReplicationDomain</B></A> |
| | | * class |
| | | */ |
| | | public class AckMessageListComparator implements Comparator<AckMessageList> |
| | | { |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public int compare(AckMessageList a1, AckMessageList a2) |
| | | { |
| | | return a1.getChangeNumber().compareTo(a2.getChangeNumber()); |
| | | } |
| | | } |
| | | @org.opends.server.types.PublicAPI( |
| | | stability=org.opends.server.types.StabilityLevel.PRIVATE) |
| | | package org.opends.server.replication.service; |
| | | |
| | |
| | | oc.add("ds-task-reset-generation-id"); |
| | | attrs.put(oc); |
| | | attrs.put("ds-task-class-name", |
| | | "org.opends.server.tasks.SetGenerationIdTask"); |
| | | "org.opends.server.replication.service.SetGenerationIdTask"); |
| | | if (isPre) |
| | | { |
| | | if (!localOnly) |
| | |
| | | oc.add("ds-task-initialize-remote-replica"); |
| | | attrs.put(oc); |
| | | attrs.put("ds-task-class-name", |
| | | "org.opends.server.tasks.InitializeTargetTask"); |
| | | "org.opends.server.replication.service.InitializeTargetTask"); |
| | | attrs.put("ds-task-initialize-domain-dn", baseDN); |
| | | attrs.put("ds-task-initialize-replica-server-id", "all"); |
| | | while (!taskCreated) |
| | |
| | | import java.io.FileReader; |
| | | import java.net.ServerSocket; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.core.AddOperationBasis; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | import org.opends.server.tools.LDAPModify; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.ResultCode; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | import static org.testng.Assert.assertEquals; |
| | | import static org.testng.Assert.assertTrue; |
| | |
| | | * The port of the replicationServer. |
| | | */ |
| | | private int replServerPort; |
| | | |
| | | |
| | | /** |
| | | * The replicationServer that will be used in this test. |
| | | */ |
| | | private static final int WINDOW_SIZE = 10; |
| | | private static final int REPLICATION_QUEUE_SIZE = 100; |
| | | private DN baseDn; |
| | | |
| | | |
| | | /** |
| | | * Before starting the tests, start the server and configure a |
| | | * replicationServer. |
| | | */ |
| | | |
| | | |
| | | @BeforeClass(alwaysRun=true) |
| | | public void setUp() throws Exception { |
| | | super.setUp(); |
| | |
| | | // replication server |
| | | String replServerLdif = |
| | | "dn: cn=Replication Server, " + SYNCHRO_PLUGIN_DN + "\n" |
| | | + "objectClass: top\n" |
| | | + "objectClass: top\n" |
| | | + "objectClass: ds-cfg-replication-server\n" |
| | | + "cn: Replication Server\n" |
| | | + "cn: Replication Server\n" |
| | | + "ds-cfg-replication-port: " + replServerPort + "\n" |
| | | + "ds-cfg-replication-db-directory: ChangeNumberControlDbTest\n" |
| | | + "ds-cfg-replication-server-id: 103\n"; |
| | |
| | | + "changetype: delete"} |
| | | }; |
| | | } |
| | | |
| | | |
| | | @Test(dataProvider="operations") |
| | | public void ChangeNumberControlTest(String request) throws Exception { |
| | | |
| | |
| | | }; |
| | | |
| | | String resultPath = TestCaseUtils.createTempFile(); |
| | | |
| | | |
| | | FileOutputStream fos = new FileOutputStream(resultPath); |
| | | |
| | | assertEquals(LDAPModify.mainModify(args, false, fos, System.err), 0); |
| | | //fos.flush(); |
| | | fos.close(); |
| | | |
| | | |
| | | assertTrue(isCsnLinePresent(resultPath)); |
| | | } |
| | | |
| | |
| | | } |
| | | return (found); |
| | | } |
| | | |
| | | /** |
| | | * Utility function. Can be used to create and add and entry |
| | | * in the local DS from its ldif description. |
| | | * |
| | | * @param entryString The entry in ldif from. |
| | | * @return The ResultCode of the operation. |
| | | * @throws Exception If something went wrong. |
| | | */ |
| | | private ResultCode addEntry(String entryString) throws Exception |
| | | { |
| | | Entry entry; |
| | | AddOperationBasis addOp; |
| | | entry = TestCaseUtils.entryFromLdifString(entryString); |
| | | addOp = new AddOperationBasis(InternalClientConnection.getRootConnection(), |
| | | InternalClientConnection.nextOperationID(), InternalClientConnection |
| | | .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(), |
| | | entry.getUserAttributes(), entry.getOperationalAttributes()); |
| | | addOp.setInternalOperation(true); |
| | | addOp.run(); |
| | | |
| | | return addOp.getResultCode(); |
| | | } |
| | | } |
| | |
| | | import org.opends.server.api.SynchronizationProvider; |
| | | import org.opends.server.backends.MemoryBackend; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.plugin.DomainFakeCfg; |
| | | import org.opends.server.replication.plugin.MultimasterReplication; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.DeleteMsg; |
| | | import org.opends.server.replication.protocol.ModifyDNMsg; |
| | |
| | | public void addModDelDependencyTest() throws Exception |
| | | { |
| | | ReplicationServer replServer = null; |
| | | ReplicationDomain domain = null; |
| | | LDAPReplicationDomain domain = null; |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | SynchronizationProvider replicationPlugin = null; |
| | | short brokerId = 2; |
| | |
| | | public void moddnDelDependencyTest() throws Exception |
| | | { |
| | | ReplicationServer replServer = null; |
| | | ReplicationDomain domain = null; |
| | | LDAPReplicationDomain domain = null; |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | SynchronizationProvider replicationPlugin = null; |
| | | short brokerId = 2; |
| | |
| | | Thread.sleep(2000); |
| | | domain = MultimasterReplication.createNewDomain(domainConf); |
| | | replicationPlugin.completeSynchronizationProvider(); |
| | | |
| | | |
| | | ReplicationBroker broker = |
| | | openReplicationSession(baseDn, brokerId, 1000, replServerPort, 1000, |
| | | false, CLEAN_DB_GENERATION_ID); |
| | |
| | | assertTrue(found, "The initial entry add failed"); |
| | | |
| | | |
| | | // disable the domain to make sure that the messages are |
| | | // disable the domain to make sure that the messages are |
| | | // all sent in a row. |
| | | domain.disable(); |
| | | |
| | |
| | | public void addDelAddDependencyTest() throws Exception |
| | | { |
| | | ReplicationServer replServer = null; |
| | | ReplicationDomain domain = null; |
| | | LDAPReplicationDomain domain = null; |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | SynchronizationProvider replicationPlugin = null; |
| | | short brokerId = 2; |
| | |
| | | public void addModdnDependencyTest() throws Exception |
| | | { |
| | | ReplicationServer replServer = null; |
| | | ReplicationDomain domain = null; |
| | | LDAPReplicationDomain domain = null; |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | SynchronizationProvider replicationPlugin = null; |
| | | short brokerId = 2; |
| | |
| | | int AddSequenceLength = 30; |
| | | |
| | | cleanDB(); |
| | | |
| | | |
| | | |
| | | try |
| | | { |
| | |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.common.ServerStatus; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.ChangeStatusMsg; |
| | | import org.opends.server.replication.protocol.DoneMsg; |
| | |
| | | import org.opends.server.replication.protocol.InitializeTargetMsg; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.replication.protocol.SocketSession; |
| | | import org.opends.server.replication.protocol.TopologyMsg; |
| | | import org.opends.server.replication.server.ReplServerFakeConfiguration; |
| | | import org.opends.server.replication.server.ReplicationBackend; |
| | | import org.opends.server.replication.server.ReplicationServer; |
| | |
| | | private ReplicationServer replServer2 = null; |
| | | private ReplicationServer replServer3 = null; |
| | | private boolean emptyOldChanges = true; |
| | | ReplicationDomain replDomain = null; |
| | | LDAPReplicationDomain replDomain = null; |
| | | private Entry taskInitRemoteS2; |
| | | SocketSession ssSession = null; |
| | | boolean ssShutdownRequested = false; |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | } |
| | |
| | | "Unable to add the synchronized server"); |
| | | configEntryList.add(synchroServerEntry.getDN()); |
| | | |
| | | replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | |
| | | |
| | | if (replDomain != null) |
| | |
| | | } |
| | | } |
| | | } |
| | | assertEquals(searchOperation.getSearchEntries().size(), expectedCount); |
| | | assertEquals(searchOperation.getSearchEntries().size(), expectedCount); |
| | | } |
| | | catch(Exception e) |
| | | { |
| | |
| | | |
| | | debugInfo(testCase + " Expect genId to be set in memory on the replication " + |
| | | " server side (not wrote on disk/db since no change occurred)."); |
| | | rgenId = replServer1.getGenerationId(baseDn); |
| | | rgenId = replServer1.getGenerationId(baseDn.toNormalizedString()); |
| | | assertEquals(rgenId, EMPTY_DN_GENID); |
| | | |
| | | // Clean for next test |
| | | debugInfo(testCase + " Unconfiguring DS1 to replicate to RS1(" + changelog1ID + ")"); |
| | | disconnectFromReplServer(changelog1ID); |
| | | |
| | | |
| | | |
| | | //=========================================================== |
| | | debugInfo(testCase + " ** TEST ** Non empty backend"); |
| | | |
| | |
| | | assertTrue(genId != EMPTY_DN_GENID); |
| | | |
| | | debugInfo(testCase + " Test that the generationId is set on RS1"); |
| | | rgenId = replServer1.getGenerationId(baseDn); |
| | | rgenId = replServer1.getGenerationId(baseDn.toNormalizedString()); |
| | | assertEquals(genId, rgenId); |
| | | |
| | | //=========================================================== |
| | |
| | | fail("Broker connection is expected to be accepted."); |
| | | } |
| | | |
| | | // Broker 2 should receive an update topo message to signal broker 3 |
| | | // arrival in topology |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive a TopologyMsg." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive a TopologyMsg."); |
| | | } |
| | | |
| | | //=========================================================== |
| | | debugInfo(testCase + " ** TEST ** DS2 (bad genID) changes must be ignored."); |
| | | |
| | |
| | | //=========================================================== |
| | | debugInfo(testCase + " ** TEST ** Persistence of the generation ID in RS1"); |
| | | |
| | | long genIdBeforeShut = replServer1.getGenerationId(baseDn); |
| | | long genIdBeforeShut = |
| | | replServer1.getGenerationId(baseDn.toNormalizedString()); |
| | | |
| | | debugInfo("Shutdown replServer1"); |
| | | broker2.stop(); |
| | |
| | | |
| | | debugInfo("Delay to allow DS to reconnect to replServer1"); |
| | | |
| | | long genIdAfterRestart = replServer1.getGenerationId(baseDn); |
| | | long genIdAfterRestart = |
| | | replServer1.getGenerationId(baseDn.toNormalizedString()); |
| | | debugInfo("Aft restart / replServer.genId=" + genIdAfterRestart); |
| | | assertTrue(replServer1!=null, "Replication server creation failed."); |
| | | assertTrue(genIdBeforeShut == genIdAfterRestart, |
| | |
| | | fail("Broker connection is expected to be accepted."); |
| | | } |
| | | |
| | | // Broker 2 should receive an update topo message to signal broker 3 |
| | | // arrival in topology |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive a TopologyMsg." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive a TopologyMsg."); |
| | | } |
| | | |
| | | debugInfo("Launch on-line import on DS1"); |
| | | long oldGenId = genId; |
| | |
| | | performLdifImport(); |
| | | connectServer1ToChangelog(changelog1ID); |
| | | |
| | | // Broker 2 and 3 should receive 2 update topo messages to signal broker 1 |
| | | // has disconnected from topology then reconnected |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive a first TopologyMsg" + |
| | | " for Broker 1 import + reconnect." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive a first TopologyMsg for DS1 import + " + |
| | | "reconnect."); |
| | | } |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker3.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 3 connection is expected to receive a first TopologyMsg" + |
| | | " for Broker 1 import + reconnect." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS3 is expected to receive a first TopologyMsg for DS1 import + " + |
| | | "reconnect."); |
| | | } |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive a second TopologyMsg" + |
| | | " for Broker 1 import + reconnect." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive a second TopologyMsg for DS1 import + " + |
| | | "reconnect."); |
| | | } |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker3.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 3 connection is expected to receive a second TopologyMsg" + |
| | | " for Broker 1 import + reconnect." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS3 is expected to receive a second TopologyMsg for DS1 import + " + |
| | | "reconnect."); |
| | | } |
| | | |
| | | debugInfo("Create Reset task on DS1 to propagate the new gen ID as the reference"); |
| | | Entry taskReset = TestCaseUtils.makeEntry( |
| | | "dn: ds-task-id=resetgenid"+genId+ UUID.randomUUID() + |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-reset-generation-id", |
| | | "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask", |
| | | "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr); |
| | | addTask(taskReset, ResultCode.SUCCESS, null); |
| | | waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null); |
| | | waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null); |
| | | |
| | | // Broker 2 and 3 should receive 1 change status message to order them |
| | | // to enter the bad gen id status |
| | |
| | | "bad gen id status."); |
| | | } |
| | | |
| | | // Broker 2 and 3 should receive 1 update topo message to signal other broker gen id |
| | | // has been resetted, and 2 topo messages to signal DS1 has been disconnected |
| | | // then reconnected |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive 1 TopologyMsg" + |
| | | " for gen id reset of broker 3." |
| | | + msg); |
| | | } |
| | | msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive 1 TopologyMsg" + |
| | | " for DS1 disconnected." |
| | | + msg); |
| | | } |
| | | msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive 1 TopologyMsg" + |
| | | " for DS1 reconnected." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive 3 TopologyMsg for gen id reset."); |
| | | } |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker3.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 3 connection is expected to receive 1 TopologyMsg" + |
| | | " for gen id reset of broker 2." |
| | | + msg); |
| | | } |
| | | msg = broker3.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 3 connection is expected to receive 1 TopologyMsg" + |
| | | " for DS1 disconnected." |
| | | + msg); |
| | | } |
| | | msg = broker3.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 3 connection is expected to receive 1 TopologyMsg" + |
| | | " for DS1 reconnected" |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS3 is expected to receive 3 TopologyMsg for gen id reset."); |
| | | } |
| | | |
| | | debugInfo("DS1 root entry must contain the new gen ID"); |
| | | genId = readGenIdFromSuffixRootEntry(); |
| | | assertTrue(genId != -1, "DS is expected to have a new genID computed " + |
| | |
| | | + "is expected to be diffrent from previous one"); |
| | | |
| | | debugInfo("RS1 must have the new gen ID"); |
| | | rgenId = replServer1.getGenerationId(baseDn); |
| | | rgenId = replServer1.getGenerationId(baseDn.toNormalizedString()); |
| | | assertEquals(genId, rgenId, "DS and replServer are expected to have same genId."); |
| | | |
| | | debugInfo("RS1 must have been cleared since it has not the proper generation ID"); |
| | | checkChangelogSize(0); |
| | | |
| | | assertTrue(!replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(!replServer1.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server1ID), |
| | | "Expecting that DS1 status in RS1 is : not in bad gen id."); |
| | | |
| | |
| | | debugInfo(testCase + " ** TEST ** Previous test set a new gen ID on the "+ |
| | | "topology, verify degradation of DS2 and DS3"); |
| | | |
| | | assertTrue(replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(replServer1.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server2ID), |
| | | "Expecting that DS2 with old gen ID is in bad gen id from RS1"); |
| | | assertTrue(replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(replServer1.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server3ID), |
| | | "Expecting that DS3 with old gen ID is in bad gen id from RS1"); |
| | | |
| | |
| | | broker3 = openReplicationSession(baseDn, |
| | | server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges, genId); |
| | | |
| | | // Broker 2 should receive 2 update topo messages to signal broker 3 |
| | | // stopped and restarted |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive a first TopologyMsg." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive a first TopologyMsg."); |
| | | } |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive a second TopologyMsg." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive a second TopologyMsg."); |
| | | } |
| | | |
| | | debugInfo("Adding reset task to DS1"); |
| | | taskReset = TestCaseUtils.makeEntry( |
| | | "dn: ds-task-id=resetgenid"+ UUID.randomUUID() + |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-reset-generation-id", |
| | | "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask", |
| | | "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr); |
| | | |
| | | addTask(taskReset, ResultCode.SUCCESS, null); |
| | | waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null); |
| | | Thread.sleep(200); |
| | | |
| | | // Broker 2 and 3 should not receive a change status as they are connected with |
| | | // the right genid, but should anyway receive a topo message to update them with potential |
| | | // updates. |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker2.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 2 connection is expected to receive 1 TopologyMsg" + |
| | | " for topo update on reset gen id." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS2 is expected to receive 1 TopologyMsg for topo update on reset gen id."); |
| | | } |
| | | try |
| | | { |
| | | ReplicationMsg msg = broker3.receive(); |
| | | if (!(msg instanceof TopologyMsg)) |
| | | { |
| | | fail("Broker 3 connection is expected to receive 1 TopologyMsg" + |
| | | " for topo update on reset gen id." |
| | | + msg); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException se) |
| | | { |
| | | fail("DS3 is expected to receive 1 TopologyMsg for topo update on reset gen id."); |
| | | } |
| | | |
| | | debugInfo("Verify that RS1 has still the right genID"); |
| | | assertEquals(replServer1.getGenerationId(baseDn), rgenId); |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), rgenId); |
| | | |
| | | // Updates count in RS1 must stay unchanged = to 1 |
| | | Thread.sleep(500); |
| | |
| | | |
| | | debugInfo("Verifying that DS2 is not in bad gen id any more"); |
| | | |
| | | assertTrue(!replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(!replServer1.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server2ID), |
| | | "Expecting that DS2 is not in bad gen id from RS1"); |
| | | |
| | | debugInfo("Verifying that DS3 is not in bad gen id any more"); |
| | | |
| | | assertTrue(!replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(!replServer1.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server3ID), |
| | | "Expecting that DS3 is not in bad gen id from RS1"); |
| | | |
| | |
| | | /* expected */ |
| | | AddMsg rcvmsg = (AddMsg)msg; |
| | | assertEquals(rcvmsg.getChangeNumber(), emsg.getChangeNumber()); |
| | | } |
| | | } |
| | | catch(SocketTimeoutException e) |
| | | { |
| | | fail("The msg send by DS2 is expected to be received by DS3)"); |
| | |
| | | Thread.sleep(1500); |
| | | |
| | | debugInfo("Expect genId are set in all replServers."); |
| | | assertEquals(replServer1.getGenerationId(baseDn), EMPTY_DN_GENID, |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID, |
| | | " in replServer1"); |
| | | assertEquals(replServer2.getGenerationId(baseDn), EMPTY_DN_GENID, |
| | | assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID, |
| | | " in replServer2"); |
| | | assertEquals(replServer3.getGenerationId(baseDn), EMPTY_DN_GENID, |
| | | assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), EMPTY_DN_GENID, |
| | | " in replServer3"); |
| | | |
| | | debugInfo("Disconnect DS from replServer1."); |
| | |
| | | |
| | | debugInfo( |
| | | "Expect genIds to be resetted in all servers to -1 as no more DS in topo"); |
| | | assertEquals(replServer1.getGenerationId(baseDn), -1); |
| | | assertEquals(replServer2.getGenerationId(baseDn), -1); |
| | | assertEquals(replServer3.getGenerationId(baseDn), -1); |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), -1); |
| | | assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), -1); |
| | | assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), -1); |
| | | |
| | | debugInfo("Add entries to DS"); |
| | | this.addTestEntriesToDB(updatedEntries); |
| | |
| | | "Expect genIds to be set in all servers based on the added entries."); |
| | | genId = readGenIdFromSuffixRootEntry(); |
| | | assertTrue(genId != -1); |
| | | assertEquals(replServer1.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer2.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer3.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | |
| | | debugInfo("Connecting broker2 to replServer3 with a good genId"); |
| | | try |
| | |
| | | |
| | | debugInfo( |
| | | "Expecting that broker2 is not in bad gen id since it has a correct genId"); |
| | | assertTrue(!replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(!replServer1.getReplicationServerDomain(baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server2ID)); |
| | | |
| | | debugInfo("Disconnecting DS from replServer1"); |
| | |
| | | |
| | | debugInfo( |
| | | "Expect all genIds to keep their value since broker2 is still connected."); |
| | | assertEquals(replServer1.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer2.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer3.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | |
| | | debugInfo("Connecting broker2 to replServer1 with a bad genId"); |
| | | try |
| | |
| | | |
| | | debugInfo( |
| | | "Expecting that broker3 is in bad gen id since it has a bad genId"); |
| | | assertTrue(replServer1.getReplicationServerDomain(baseDn, false). |
| | | assertTrue(replServer1.getReplicationServerDomain(baseDn.toNormalizedString(), false). |
| | | isDegradedDueToGenerationId(server3ID)); |
| | | |
| | | int found = testEntriesInDb(); |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-reset-generation-id", |
| | | "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask", |
| | | "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr); |
| | | addTask(taskReset, ResultCode.SUCCESS, null); |
| | | waitTaskState(taskReset, TaskState.COMPLETED_SUCCESSFULLY, null); |
| | |
| | | |
| | | debugInfo("Verifying that all replservers genIds have been reset."); |
| | | genId = readGenIdFromSuffixRootEntry(); |
| | | assertEquals(replServer1.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer2.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer3.getGenerationId(baseDn), genId); |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), genId); |
| | | |
| | | debugInfo("Adding reset task to DS."); |
| | | taskReset = TestCaseUtils.makeEntry( |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-reset-generation-id", |
| | | "ds-task-class-name: org.opends.server.tasks.SetGenerationIdTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.SetGenerationIdTask", |
| | | "ds-task-reset-generation-id-domain-base-dn: " + baseDnStr, |
| | | "ds-task-reset-generation-id-new-value: -1"); |
| | | addTask(taskReset, ResultCode.SUCCESS, null); |
| | |
| | | |
| | | debugInfo("Verifying that all replservers genIds have been reset."); |
| | | genId = readGenIdFromSuffixRootEntry(); |
| | | assertEquals(replServer1.getGenerationId(baseDn), -1); |
| | | assertEquals(replServer2.getGenerationId(baseDn), -1); |
| | | assertEquals(replServer3.getGenerationId(baseDn), -1); |
| | | assertEquals(replServer1.getGenerationId(baseDn.toNormalizedString()), -1); |
| | | assertEquals(replServer2.getGenerationId(baseDn.toNormalizedString()), -1); |
| | | assertEquals(replServer3.getGenerationId(baseDn.toNormalizedString()), -1); |
| | | |
| | | debugInfo( |
| | | "Disconnect DS from replServer1 (required in order to DEL entries)."); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Loop opening sessions to the Replication Server |
| | | * to check that it handle correctly deconnection and reconnection. |
| | | * Loop opening sessions to the Replication Server |
| | | * to check that it handle correctly deconnection and reconnection. |
| | | */ |
| | | @Test(enabled=false, groups="slow") |
| | | public void testLoop() throws Exception |
| | |
| | | long generationId = 1000+i; |
| | | broker = openReplicationSession(baseDn, |
| | | server2ID, 100, getChangelogPort(changelog1ID), |
| | | 1000, !emptyOldChanges, generationId); |
| | | 1000, !emptyOldChanges, generationId); |
| | | debugInfo(testCase + " Expect genId to be set in memory on the replication " + |
| | | " server side even if not wrote on disk/db since no change occurred."); |
| | | rgenId = replServer1.getGenerationId(baseDn); |
| | | " server side even if not wrote on disk/db since no change occurred."); |
| | | rgenId = replServer1.getGenerationId(baseDn.toNormalizedString()); |
| | | assertEquals(rgenId, generationId); |
| | | broker.stop(); |
| | | broker = null; |
| | |
| | | postTest(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This is used to make sure that the 3 tests are run in the |
| | | * This is used to make sure that the 3 tests are run in the |
| | | * specified order since this is necessary. |
| | | */ |
| | | @Test(enabled=true, groups="slow") |
| | |
| | | import org.opends.messages.Severity; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.replication.protocol.DoneMsg; |
| | | import org.opends.server.replication.protocol.EntryMsg; |
| | | import org.opends.server.replication.protocol.ErrorMsg; |
| | |
| | | ReplicationServer changelog2 = null; |
| | | ReplicationServer changelog3 = null; |
| | | boolean emptyOldChanges = true; |
| | | ReplicationDomain replDomain = null; |
| | | LDAPReplicationDomain replDomain = null; |
| | | |
| | | private void log(String s) |
| | | { |
| | |
| | | // re-enabled and this clears the backend reference and thus the underlying |
| | | // data. So for this particular test, we use a classical backend. Let's |
| | | // clear it. |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | updatedEntries = newLDIFEntries(); |
| | | |
| | |
| | | connection = InternalClientConnection.getRootConnection(); |
| | | |
| | | synchroServerEntry = null; |
| | | replServerEntry = null; |
| | | replServerEntry = null; |
| | | |
| | | taskInitFromS2 = TestCaseUtils.makeEntry( |
| | | "dn: ds-task-id=" + UUID.randomUUID() + |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: " + EXAMPLE_DN, |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask", |
| | | "ds-task-initialize-domain-dn: " + EXAMPLE_DN, |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask", |
| | | "ds-task-initialize-domain-dn: " + EXAMPLE_DN, |
| | | "ds-task-initialize-replica-server-id: all"); |
| | | } |
| | |
| | | { |
| | | break; |
| | | } |
| | | Thread.sleep(10); |
| | | Thread.sleep(100); |
| | | } |
| | | } while (completionTime == null); |
| | | |
| | |
| | | // Send entries |
| | | try |
| | | { |
| | | RoutableMsg initTargetMessage = new InitializeTargetMsg( |
| | | baseDn, server2ID, destinationServerID, requestorID, updatedEntries.length); |
| | | RoutableMsg initTargetMessage = |
| | | new InitializeTargetMsg( |
| | | EXAMPLE_DN, server2ID, destinationServerID, requestorID, |
| | | updatedEntries.length); |
| | | broker.publish(initTargetMessage); |
| | | |
| | | for (String entry : updatedEntries) |
| | |
| | | { |
| | | EntryMsg em = (EntryMsg)msg; |
| | | log("Broker " + serverID + " receives entry " + new String(em.getEntryBytes())); |
| | | entriesReceived++; |
| | | entriesReceived+=countEntryLimits(em.getEntryBytes()); |
| | | } |
| | | else if (msg instanceof DoneMsg) |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Count the number of entries in the provided byte[]. |
| | | * This is based on the hypothesis that the entries are separated |
| | | * by a "\n\n" String. |
| | | * |
| | | * @param entryBytes |
| | | * @return The number of entries in the provided byte[]. |
| | | */ |
| | | private int countEntryLimits(byte[] entryBytes) |
| | | { |
| | | int entryCount = 0; |
| | | int count = 0; |
| | | while (count<=entryBytes.length-2) |
| | | { |
| | | if ((entryBytes[count] == '\n') && (entryBytes[count+1] == '\n')) |
| | | { |
| | | entryCount++; |
| | | count++; |
| | | } |
| | | count++; |
| | | } |
| | | return entryCount; |
| | | } |
| | | |
| | | /** |
| | | * Creates a new replicationServer. |
| | | * @param changelogId The serverID of the replicationServer to create. |
| | | * @return The new replicationServer. |
| | |
| | | ReplServerFakeConfiguration conf = |
| | | new ReplServerFakeConfiguration( |
| | | getChangelogPort(changelogId), |
| | | "initOnlineTest" + getChangelogPort(changelogId) + testCase + "Db", |
| | | "initOnlineTest" + getChangelogPort(changelogId) + testCase + "Db", |
| | | 0, |
| | | changelogId, |
| | | 0, |
| | |
| | | |
| | | |
| | | // Clear the backend |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif); |
| | | DirectoryServer.getConfigHandler().addEntry(synchroServerEntry, null); |
| | |
| | | "Unable to add the synchronized server"); |
| | | configEntryList.add(synchroServerEntry.getDN()); |
| | | |
| | | replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | |
| | | assertTrue(!replDomain.ieRunning(), |
| | | "ReplicationDomain: Import/Export is not expected to be running"); |
| | |
| | | server2 = openReplicationSession(DN.decode(EXAMPLE_DN), |
| | | server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); |
| | | |
| | | Thread.sleep(2000); |
| | | // Thread.sleep(2000); |
| | | |
| | | // In S1 launch the total update |
| | | addTask(taskInitFromS2, ResultCode.SUCCESS, null); |
| | |
| | | catch(Exception e) |
| | | { |
| | | fail(testCase + " Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); |
| | | } finally |
| | | } finally |
| | | { |
| | | afterTest(); |
| | | } |
| | |
| | | String testCase = "initializeExport"; |
| | | |
| | | log("Starting "+testCase); |
| | | |
| | | |
| | | try |
| | | { |
| | | changelog1 = createChangelogServer(changelog1ID, testCase); |
| | |
| | | server2 = openReplicationSession(DN.decode(EXAMPLE_DN), |
| | | server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); |
| | | |
| | | Thread.sleep(3000); |
| | | // Not needed anymore since OpenReplicationSession |
| | | // checks for session establishment ? |
| | | // Thread.sleep(3000); |
| | | |
| | | InitializeRequestMsg initMsg = new InitializeRequestMsg(baseDn, |
| | | InitializeRequestMsg initMsg = new InitializeRequestMsg(EXAMPLE_DN, |
| | | server2ID, server1ID); |
| | | server2.publish(initMsg); |
| | | |
| | |
| | | String testCase = "initializeTargetExport"; |
| | | |
| | | log("Starting " + testCase); |
| | | |
| | | |
| | | try |
| | | { |
| | | |
| | |
| | | server2 = openReplicationSession(DN.decode(EXAMPLE_DN), |
| | | server2ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); |
| | | |
| | | Thread.sleep(1000); |
| | | // Thread.sleep(1000); |
| | | |
| | | // Launch in S1 the task that will initialize S2 |
| | | addTask(taskInitTargetS2, ResultCode.SUCCESS, null); |
| | |
| | | server3 = openReplicationSession(DN.decode(EXAMPLE_DN), |
| | | server3ID, 100, getChangelogPort(changelog1ID), 1000, emptyOldChanges); |
| | | |
| | | Thread.sleep(1000); |
| | | // Thread.sleep(1000); |
| | | |
| | | // Launch in S1 the task that will initialize S2 |
| | | addTask(taskInitTargetAll, ResultCode.SUCCESS, null); |
| | |
| | | // S2 publishes entries to S1 |
| | | makeBrokerPublishEntries(server2, server2ID, server1ID, server2ID); |
| | | |
| | | Thread.sleep(10000); // FIXME - how to know the import is done |
| | | // wait until the replication domain has expected generationID |
| | | // this should indicate that the import occured correctly. |
| | | for (int count = 0; count < 100; count++) |
| | | { |
| | | if (replDomain.getGenerationID() == 56869) |
| | | break; |
| | | Thread.sleep(200); |
| | | } |
| | | |
| | | // Test that entries have been imported in S1 |
| | | testEntriesInDb(); |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask", |
| | | "ds-task-initialize-domain-dn: foo", |
| | | "ds-task-initialize-remote-replica-server-id: " + server2ID); |
| | | addTask(taskInitTarget, ResultCode.INVALID_DN_SYNTAX, |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask", |
| | | "ds-task-initialize-domain-dn: dc=foo", |
| | | "ds-task-initialize-remote-replica-server-id: " + server2ID); |
| | | addTask(taskInitTarget, ResultCode.OTHER, |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: foo", |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | addTask(taskInit, ResultCode.INVALID_DN_SYNTAX, |
| | | ERR_TASK_INITIALIZE_INVALID_DN.get()); |
| | | ERR_NO_MATCHING_DOMAIN.get()); |
| | | |
| | | // Domain base dn not related to any domain |
| | | taskInit = TestCaseUtils.makeEntry( |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: dc=foo", |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | addTask(taskInit, ResultCode.OTHER, ERR_NO_MATCHING_DOMAIN.get()); |
| | | addTask(taskInit, ResultCode.INVALID_DN_SYNTAX, |
| | | ERR_NO_MATCHING_DOMAIN.get()); |
| | | |
| | | // Invalid Source |
| | | taskInit = TestCaseUtils.makeEntry( |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: -3"); |
| | | addTask(taskInit, ResultCode.OTHER, |
| | |
| | | |
| | | // Check that the list of connected LDAP servers is correct |
| | | // in each replication servers |
| | | List<String> l1 = changelog1.getReplicationServerDomain(baseDn, false). |
| | | List<String> l1 = changelog1.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false). |
| | | getConnectedLDAPservers(); |
| | | assertEquals(l1.size(), 1); |
| | | assertEquals(l1.get(0), String.valueOf(server1ID)); |
| | | |
| | | List<String> l2; |
| | | l2 = changelog2.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers(); |
| | | l2 = changelog2.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false).getConnectedLDAPservers(); |
| | | assertEquals(l2.size(), 2); |
| | | assertTrue(l2.contains(String.valueOf(server2ID))); |
| | | assertTrue(l2.contains(String.valueOf(server3ID))); |
| | | |
| | | List<String> l3; |
| | | l3 = changelog3.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers(); |
| | | l3 = changelog3.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false).getConnectedLDAPservers(); |
| | | assertEquals(l3.size(), 0); |
| | | |
| | | // Test updates |
| | | broker3.stop(); |
| | | Thread.sleep(1000); |
| | | l2 = changelog2.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers(); |
| | | l2 = changelog2.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false).getConnectedLDAPservers(); |
| | | assertEquals(l2.size(), 1); |
| | | assertEquals(l2.get(0), String.valueOf(server2ID)); |
| | | |
| | |
| | | server3ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges); |
| | | broker2.stop(); |
| | | Thread.sleep(1000); |
| | | l2 = changelog2.getReplicationServerDomain(baseDn, false).getConnectedLDAPservers(); |
| | | l2 = changelog2.getReplicationServerDomain( |
| | | baseDn.toNormalizedString(), false).getConnectedLDAPservers(); |
| | | assertEquals(l2.size(), 1); |
| | | assertEquals(l2.get(0), String.valueOf(server3ID)); |
| | | |
| | |
| | | afterTest(); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Test(enabled=true, groups="slow") |
| | | public void initializeTargetExportMultiSS() throws Exception |
| | | { |
| | |
| | | server2ID, 100, getChangelogPort(changelog2ID), 1000, emptyOldChanges); |
| | | } |
| | | |
| | | Thread.sleep(1000); |
| | | // Thread.sleep(1000); |
| | | |
| | | // Launch in S1 the task that will initialize S2 |
| | | addTask(taskInitTargetS2, ResultCode.SUCCESS, null); |
| | |
| | | log(testCase + " Will connect server 2 to " + changelog2ID); |
| | | server2 = openReplicationSession(DN.decode(EXAMPLE_DN), |
| | | server2ID, 100, getChangelogPort(changelog2ID), |
| | | 1000, emptyOldChanges, changelog1.getGenerationId(baseDn)); |
| | | 1000, emptyOldChanges, |
| | | changelog1.getGenerationId(baseDn.toNormalizedString())); |
| | | } |
| | | |
| | | // Connect a broker acting as server 3 to Repl Server 3 |
| | |
| | | log(testCase + " Will connect server 3 to " + changelog3ID); |
| | | server3 = openReplicationSession(DN.decode(EXAMPLE_DN), |
| | | server3ID, 100, getChangelogPort(changelog3ID), |
| | | 1000, emptyOldChanges, changelog1.getGenerationId(baseDn)); |
| | | 1000, emptyOldChanges, |
| | | changelog1.getGenerationId(baseDn.toNormalizedString())); |
| | | } |
| | | |
| | | Thread.sleep(500); |
| | | // Thread.sleep(500); |
| | | |
| | | // S3 sends init request |
| | | log(testCase + " server 3 Will send reqinit to " + server1ID); |
| | | InitializeRequestMsg initMsg = |
| | | new InitializeRequestMsg(baseDn, server3ID, server1ID); |
| | | new InitializeRequestMsg(EXAMPLE_DN, server3ID, server1ID); |
| | | server3.publish(initMsg); |
| | | |
| | | // S3 should receive target, entries & done |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: " + 20); |
| | | |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: " + server1ID); |
| | | |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTargetTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTargetTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: " + 0); |
| | | |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | |
| | |
| | | "objectclass: top", |
| | | "objectclass: ds-task", |
| | | "objectclass: ds-task-initialize-from-remote-replica", |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask", |
| | | "ds-task-class-name: org.opends.server.replication.service.InitializeTask", |
| | | "ds-task-initialize-domain-dn: " + baseDn, |
| | | "ds-task-initialize-replica-server-id: " + server2ID); |
| | | |
| | |
| | | super.classCleanUp(); |
| | | |
| | | // Clear the backend |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | paranoiaCheck(); |
| | | } |
| | | } |
| | |
| | | import static org.testng.Assert.assertEquals; |
| | | import static org.testng.Assert.assertTrue; |
| | | |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | |
| | | import java.net.ServerSocket; |
| | | import java.net.SocketTimeoutException; |
| | | import java.util.Iterator; |
| | |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.replication.server.ReplServerFakeConfiguration; |
| | |
| | | import org.opends.server.core.AddOperationBasis; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.testng.annotations.AfterClass; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | import static org.opends.server.TestCaseUtils.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | import static org.opends.server.loggers.debug.DebugLogger.getTracer; |
| | |
| | | // data. So for this particular test, we use a classical backend. Let's |
| | | // clear it and create the root entry |
| | | |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | addEntry("dn: dc=example,dc=com\n" + "objectClass: top\n" |
| | | + "objectClass: domain\n"); |
| | | |
| | |
| | | + "objectClass: ds-cfg-replication-server\n" |
| | | + "cn: Replication Server\n" |
| | | + "ds-cfg-replication-port:" + replServerPort + "\n" |
| | | + "ds-cfg-replication-db-directory: ReSyncTest\n" |
| | | + "ds-cfg-replication-db-directory: ReSyncTest\n" |
| | | + "ds-cfg-replication-server-id: 104\n"; |
| | | replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif); |
| | | |
| | |
| | | entry.getUserAttributes(), entry.getOperationalAttributes()); |
| | | addOp.setInternalOperation(true); |
| | | addOp.run(); |
| | | |
| | | |
| | | entryList.add(entry.getDN()); |
| | | return addOp.getResultCode(); |
| | | } |
| | |
| | | if (getEntry(DN.decode("dc=fooUniqueName2," + EXAMPLE_DN), 30000, true) == null) |
| | | fail("The Directory has not been resynchronized after the restore."); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Clean up the environment. |
| | | * |
| | |
| | | super.classCleanUp(); |
| | | |
| | | // Clear the backend |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | paranoiaCheck(); |
| | | } |
| | |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.plugin.PersistentServerState; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.replication.protocol.ErrorMsg; |
| | | import org.opends.server.replication.protocol.ReplSessionSecurity; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | |
| | | // TestCaseUtils.initializeTestBackend(true). |
| | | // (using the default TestCaseUtils.TEST_ROOT_DN_STRING suffix) |
| | | protected static final long TEST_DN_WITH_ROOT_ENTRY_GENID = 5095L; |
| | | |
| | | |
| | | /** |
| | | * Generation id for a fully empty domain. |
| | | */ |
| | |
| | | |
| | | // Call the paranoiaCheck at test cleanup or not. |
| | | // Must not been touched except if sub class has its own clean up code, |
| | | // for instance: |
| | | // @AfterClass |
| | | // for instance: |
| | | // @AfterClass |
| | | // public void classCleanUp() throws Exception |
| | | // { |
| | | // callParanoiaCheck = false; |
| | |
| | | long genId = TEST_DN_WITH_ROOT_ENTRY_GENID; |
| | | try |
| | | { |
| | | ReplicationDomain replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | genId = replDomain.getGenerationId(); |
| | | LDAPReplicationDomain replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | genId = replDomain.getGenerationID(); |
| | | } |
| | | catch(Exception e) {} |
| | | return genId; |
| | |
| | | long generationId) |
| | | throws Exception, SocketException |
| | | { |
| | | ServerState state; |
| | | ServerState state = new ServerState(); |
| | | |
| | | if (emptyOldChanges) |
| | | state = new PersistentServerState(baseDn, serverId); |
| | | else |
| | | state = new ServerState(); |
| | | new PersistentServerState(baseDn, serverId, new ServerState()); |
| | | |
| | | ReplicationBroker broker = new ReplicationBroker(null, |
| | | state, baseDn, serverId, 0, 0, 0, 0, |
| | | window_size, 0, generationId, getReplSessionSecurity(), (byte)1); |
| | | state, baseDn.toNormalizedString(), serverId, window_size, |
| | | generationId, 100000, getReplSessionSecurity(), (byte)1); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | | servers.add("localhost:" + port); |
| | | broker.start(servers); |
| | |
| | | throws Exception, SocketException |
| | | { |
| | | ReplicationBroker broker = new ReplicationBroker(null, |
| | | state, baseDn, serverId, 0, 0, 0, 0, window_size, 0, generationId, |
| | | getReplSessionSecurity(), (byte)1); |
| | | state, baseDn.toNormalizedString(), serverId, window_size, generationId, |
| | | 100000, getReplSessionSecurity(), (byte)1); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | | servers.add("localhost:" + port); |
| | | broker.start(servers); |
| | |
| | | boolean emptyOldChanges, long generationId) |
| | | throws Exception, SocketException |
| | | { |
| | | ServerState state; |
| | | ServerState state = new ServerState(); |
| | | |
| | | if (emptyOldChanges) |
| | | state = new PersistentServerState(baseDn, serverId); |
| | | else |
| | | state = new ServerState(); |
| | | new PersistentServerState(baseDn, serverId, new ServerState()); |
| | | |
| | | ReplicationBroker broker = new ReplicationBroker(null, |
| | | state, baseDn, serverId, maxRcvQueue, 0, |
| | | maxSendQueue, 0, window_size, 0, generationId, |
| | | getReplSessionSecurity(), (byte)1); |
| | | state, baseDn.toNormalizedString(), serverId, window_size, |
| | | generationId, 0, getReplSessionSecurity(), (byte)1); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | | servers.add("localhost:" + port); |
| | | broker.start(servers); |
| | |
| | | protected long getMonitorAttrValue(DN baseDn, String attr) throws Exception |
| | | { |
| | | String monitorFilter = |
| | | "(&(cn=replication plugin*)(base-dn=" + baseDn + "))"; |
| | | "(&(cn=replication Domain*)(base-dn=" + baseDn + "))"; |
| | | |
| | | InternalSearchOperation op; |
| | | int count = 0; |
| | |
| | | { |
| | | fail("addEntries Exception:"+ e.getMessage() + " " + stackTraceToSingleLineString(e)); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.core.ModifyOperationBasis; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.protocol.DeleteMsg; |
| | | import org.opends.server.replication.protocol.ModifyMsg; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.types.Attribute; |
| | |
| | | + "objectClass: ds-cfg-replication-server\n" |
| | | + "cn: Replication Server\n" |
| | | + "ds-cfg-replication-port: " + replServerPort + "\n" |
| | | + "ds-cfg-replication-db-directory: SchemaReplicationTest\n" |
| | | + "ds-cfg-replication-db-directory: SchemaReplicationTest\n" |
| | | + "ds-cfg-replication-server-id: 105\n"; |
| | | replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif); |
| | | |
| | |
| | | * Checks that changes done to the schema files are pushed to the |
| | | * ReplicationServers and that the ServerState is updated in the schema |
| | | * file. |
| | | * FIXME: This test is disabled because it has side effects. |
| | | * It causes schema tests in org.opends.server.core.AddOperationTestCase |
| | | * to fail when running the build test target. |
| | | */ |
| | | @Test(enabled=true, dependsOnMethods = { "replaySchemaChange" }) |
| | | public void pushSchemaFilesChange() throws Exception |
| | |
| | | |
| | | ReplicationBroker broker = |
| | | openReplicationSession(baseDn, (short) 3, 100, replServerPort, 5000, true); |
| | | |
| | | |
| | | try |
| | | { |
| | | // create a schema change Notification |
| | |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.types.Attribute; |
| | |
| | | + "objectClass: ds-cfg-replication-server\n" |
| | | + "cn: Replication Server\n" |
| | | + "ds-cfg-replication-port: " + replServerPort + "\n" |
| | | + "ds-cfg-replication-db-directory: StressTest\n" |
| | | + "ds-cfg-replication-db-directory: StressTest\n" |
| | | + "ds-cfg-replication-server-id: 106\n"; |
| | | replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif); |
| | | |
| | |
| | | import org.opends.server.protocols.ldap.LDAPModification; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.DeleteMsg; |
| | | import org.opends.server.replication.protocol.HeartbeatThread; |
| | |
| | | + "objectClass: ds-cfg-replication-server\n" |
| | | + "cn: Replication Server\n" |
| | | + "ds-cfg-replication-port: " + replServerPort + "\n" |
| | | + "ds-cfg-replication-db-directory: UpdateOperationTest\n" |
| | | + "ds-cfg-replication-db-directory: UpdateOperationTest\n" |
| | | + "ds-cfg-replication-server-id: 107\n"; |
| | | replServerEntry = TestCaseUtils.entryFromLdifString(replServerLdif); |
| | | |
| | |
| | | // because the conflict has been automatically resolved. |
| | | assertEquals(DummyAlertHandler.getAlertCount(), AlertCount, |
| | | "An alert was incorrectly generated when resolving conflicts"); |
| | | |
| | | |
| | | /* |
| | | * Test that modify conflict resolution is able to detect that |
| | | * Test that modify conflict resolution is able to detect that |
| | | * because there is a conflict between a MODIFYDN and a MODIFY, |
| | | * when a MODIFY is replayed the attribute that is being modified is |
| | | * now the RDN of the entry and therefore should not be deleted. |
| | | */ |
| | | // send a modify operation attempting to replace the RDN entry |
| | | // send a modify operation attempting to replace the RDN entry |
| | | // with a new value |
| | | mods = generatemods("uid", "AnotherUid"); |
| | | modMsg = new ModifyMsg(gen.newChangeNumber(), |
| | | personWithUUIDEntry.getDN(), mods, |
| | | user1entryUUID); |
| | | |
| | | |
| | | updateMonitorCount(baseDn, resolvedMonitorAttr); |
| | | AlertCount = DummyAlertHandler.getAlertCount(); |
| | | broker.publish(modMsg); |
| | | |
| | | |
| | | // check that the modify has been applied. |
| | | found = checkEntryHasAttribute(personWithUUIDEntry.getDN(), |
| | | "uid", "AnotherUid", 10000, true); |
| | | |
| | | |
| | | if (found == false) |
| | | fail("The modification has not been correctly replayed."); |
| | | assertEquals(getMonitorDelta(), 1); |
| | | |
| | | |
| | | /* |
| | | * Test that the conflict resolution code is able to detect |
| | | * that an entry has been renamed and that a new entry has |
| | |
| | | assertNotNull(resultEntry, |
| | | "The ADD replication message was NOT applied under ou=baseDn2,"+baseDn); |
| | | assertEquals(getMonitorDelta(), 1); |
| | | |
| | | |
| | | // Check that there was no administrative alert generated |
| | | // because the conflict has been automatically resolved. |
| | | assertEquals(DummyAlertHandler.getAlertCount(), AlertCount, |
| | | "An alert was incorrectly generated when resolving conflicts"); |
| | | |
| | | |
| | | |
| | | // |
| | | // Check that when a delete is conflicting with Add of some entries |
| | | // below the deleted entries, the child entry that have been added |
| | | // before the deleted is replayed gets renamed correctly. |
| | | // |
| | | |
| | | |
| | | // add domain1 entry with 2 children : domain2 and domain3 |
| | | addEntry(domain1); |
| | | domain1uid = getEntryUUID(DN.decode(domain1dn)); |
| | |
| | | "entryUUID = " + domain2uid + "+dc=domain2,ou=people," + TEST_ROOT_DN_STRING); |
| | | DN conflictDomain3dn = DN.decode( |
| | | "entryUUID = " + domain3uid + "+dc=domain3,ou=people," + TEST_ROOT_DN_STRING); |
| | | |
| | | |
| | | updateMonitorCount(baseDn, unresolvedMonitorAttr); |
| | | AlertCount = DummyAlertHandler.getAlertCount(); |
| | | |
| | | |
| | | // delete domain1 |
| | | delMsg = new DeleteMsg(domain1dn, gen.newChangeNumber(), domain1uid); |
| | | broker.publish(delMsg); |
| | |
| | | // check that the domain1 has correctly been deleted |
| | | assertNull(getEntry(DN.decode(domain1dn), 10000, false), |
| | | "The DELETE replication message was not replayed"); |
| | | |
| | | |
| | | // check that domain2 and domain3 have been renamed |
| | | assertNotNull(getEntry(conflictDomain2dn, 1000, true), |
| | | "The conflicting entries were not created"); |
| | |
| | | |
| | | // check that the 2 conflicting entries have been correctly marked |
| | | assertTrue(checkEntryHasAttribute(conflictDomain2dn, |
| | | ReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true)); |
| | | LDAPReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true)); |
| | | assertTrue(checkEntryHasAttribute(conflictDomain3dn, |
| | | ReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true)); |
| | | |
| | | LDAPReplicationDomain.DS_SYNC_CONFLICT, domain1dn, 1000, true)); |
| | | |
| | | // check that unresolved conflict count has been incremented |
| | | assertEquals(getMonitorDelta(), 1); |
| | | |
| | | |
| | | // Check that an administrative alert was generated |
| | | // because the conflict has not been automatically resolved. |
| | | assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+2, |
| | | "An alert was incorrectly generated when resolving conflicts"); |
| | | |
| | | |
| | | |
| | | // delete the resulting entries for the next test |
| | | delEntry(conflictDomain2dn); |
| | | delEntry(conflictDomain3dn); |
| | | |
| | | |
| | | // |
| | | // Check that when an entry is added on one master below an entry |
| | | // that is currently deleted on another master, the replay of the |
| | |
| | | domain2.getObjectClassAttribute(), |
| | | domain2.getAttributes(), new ArrayList<Attribute>()); |
| | | broker.publish(addMsg); |
| | | |
| | | |
| | | // check that conflict entry was created |
| | | assertNotNull(getEntry(conflictDomain2dn, 1000, true), |
| | | "The conflicting entries were not created"); |
| | | |
| | | |
| | | // check that the entry have been correctly marked as conflicting. |
| | | assertTrue(checkEntryHasAttribute(conflictDomain2dn, |
| | | ReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true)); |
| | | |
| | | LDAPReplicationDomain.DS_SYNC_CONFLICT, domain2dn, 1000, true)); |
| | | |
| | | // check that unresolved conflict count has been incremented |
| | | assertEquals(getMonitorDelta(), 1); |
| | | |
| | | |
| | | // Check that when an entry is deleted on a first master and |
| | | // renamed on a second master and the rename is replayed last |
| | | // this is correctly detected as a resolved conflict. |
| | |
| | | // if the monitor counter did not get incremented after 200sec |
| | | // then something got wrong. |
| | | assertTrue(count < 200); |
| | | |
| | | |
| | | // Check that there was no administrative alert generated |
| | | // because the conflict has been automatically resolved. |
| | | assertEquals(DummyAlertHandler.getAlertCount(), AlertCount, |
| | | "An alert was incorrectly generated when resolving conflicts"); |
| | | |
| | | /* |
| | | * Check that a conflict is detected when an entry is |
| | | * Check that a conflict is detected when an entry is |
| | | * moved below an entry that does not exist. |
| | | */ |
| | | updateMonitorCount(baseDn, unresolvedMonitorAttr); |
| | |
| | | "uid=wrong, ou=people," + TEST_ROOT_DN_STRING, |
| | | "uid=newrdn"); |
| | | broker.publish(modDnMsg); |
| | | |
| | | |
| | | count = 0; |
| | | while ((count<2000) && getMonitorDelta() == 0) |
| | | { |
| | |
| | | // if the monitor counter did not get incremented after 200sec |
| | | // then something got wrong. |
| | | assertTrue(count < 200); |
| | | |
| | | |
| | | // check that the entry have been correctly marked as conflicting. |
| | | assertTrue(checkEntryHasAttribute( |
| | | DN.decode("uid=new person,ou=baseDn2,"+baseDn), |
| | | ReplicationDomain.DS_SYNC_CONFLICT, |
| | | LDAPReplicationDomain.DS_SYNC_CONFLICT, |
| | | "uid=newrdn,ou=baseDn2,ou=People," + TEST_ROOT_DN_STRING, 1000, true)); |
| | | |
| | | broker.stop(); |
| | |
| | | |
| | | if (found == false) |
| | | fail("The modification has not been correctly replayed."); |
| | | |
| | | |
| | | // Test that replication is able to add attribute that do |
| | | // not exist in the schema. |
| | | List<Modification> invalidMods = generatemods("badattribute", "value"); |
| | |
| | | "Starting replication test : infiniteReplayLoop")); |
| | | |
| | | final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING); |
| | | |
| | | |
| | | Thread.sleep(2000); |
| | | ReplicationBroker broker = |
| | | openReplicationSession(baseDn, (short) 11, 100, replServerPort, 1000, true); |
| | |
| | | "Starting synchronization test : CNGeneratorAdjust")); |
| | | |
| | | final DN baseDn = DN.decode("ou=People," + TEST_ROOT_DN_STRING); |
| | | |
| | | |
| | | /* |
| | | * Open a session to the replicationServer using the broker API. |
| | | * This must use a different serverId to that of the directory server. |
| | |
| | | user3UUID, |
| | | baseUUID, |
| | | user3Entry.getObjectClassAttribute(), |
| | | user3Entry.getAttributes(), |
| | | user3Entry.getAttributes(), |
| | | new ArrayList<Attribute>()); |
| | | broker.publish(addMsg); |
| | | |
| | |
| | | List<Modification> mods = generatemods("telephonenumber", "01 02 45"); |
| | | ModifyOperationBasis modOp = new ModifyOperationBasis( |
| | | connection, |
| | | InternalClientConnection.nextOperationID(), |
| | | InternalClientConnection.nextMessageID(), |
| | | InternalClientConnection.nextOperationID(), |
| | | InternalClientConnection.nextMessageID(), |
| | | null, |
| | | user3Entry.getDN(), |
| | | mods); |
| | |
| | | assertTrue(msg instanceof ModifyMsg, |
| | | "The received replication message is not a MODIFY msg"); |
| | | ModifyMsg modMsg = (ModifyMsg) msg; |
| | | assertEquals(addMsg.getChangeNumber().getTimeSec(), |
| | | assertEquals(addMsg.getChangeNumber().getTimeSec(), |
| | | modMsg.getChangeNumber().getTimeSec(), |
| | | "The MOD timestamp should have been adjusted to the ADD one"); |
| | | |
| | | |
| | | // Delete the entries to clean the database. |
| | | DeleteMsg delMsg = |
| | | new DeleteMsg( |
| New file |
| | |
| | | /* |
| | | * 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-2008 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.ServerSocket; |
| | | import java.net.Socket; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.StringTokenizer; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.messages.Category; |
| | | import org.opends.messages.Message; |
| | | import org.opends.messages.Severity; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.core.AddOperationBasis; |
| | | import org.opends.server.core.DeleteOperationBasis; |
| | | import org.opends.server.core.DirectoryServer; |
| | | 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.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.ReplSessionSecurity; |
| | | import org.opends.server.replication.protocol.ServerStartMsg; |
| | | 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.loggers.debug.DebugTracer; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | import org.opends.server.replication.protocol.AckMsg; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.ByteStringFactory; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchScope; |
| | | import org.testng.annotations.Test; |
| | | import static org.testng.Assert.assertNotNull; |
| | | import static org.testng.Assert.assertNull; |
| | | import static org.opends.server.TestCaseUtils.*; |
| | | import static org.testng.Assert.fail; |
| | | import static org.testng.Assert.assertEquals; |
| | | import static org.testng.Assert.assertTrue; |
| | | 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; |
| | | |
| | | /** |
| | | * Test the client part (plugin) of the assured feature in both safe data and |
| | | * safe read modes. We use a fake RS and control the behaviour of the client |
| | | * DS (timeout, wait for acks, error handling...) |
| | | * Also check for monitoring values for assured replication |
| | | */ |
| | | public class AssuredReplicationPluginTest |
| | | extends ReplicationTestCase |
| | | { |
| | | |
| | | /** |
| | | * The port of the replicationServer. |
| | | */ |
| | | private int replServerPort; |
| | | private byte RS_SERVER_ID = (byte) 90; |
| | | // Sleep time of the RS, before sending an ack |
| | | private static final long NO_TIMEOUT_RS_SLEEP_TIME = 2000; |
| | | private String testName = this.getClass().getSimpleName(); |
| | | |
| | | // Create to distincts base dn, one for safe data replication, the other one |
| | | // for safe read replication |
| | | private String SAFE_DATA_DN = "ou=safe-data," + TEST_ROOT_DN_STRING; |
| | | private String SAFE_READ_DN = "ou=safe-read," + TEST_ROOT_DN_STRING; |
| | | private String NOT_ASSURED_DN = "ou=not-assured," + TEST_ROOT_DN_STRING; |
| | | private Entry safeDataDomainCfgEntry = null; |
| | | private Entry safeReadDomainCfgEntry = null; |
| | | private Entry notAssuredDomainCfgEntry = null; |
| | | |
| | | // The fake replication server |
| | | private FakeReplicationServer replicationServer = null; |
| | | |
| | | // Definitions for the scenario the RS supports |
| | | private static final int NOT_ASSURED_SCENARIO = 1; |
| | | private static final int TIMEOUT_SCENARIO = 2; |
| | | private static final int NO_TIMEOUT_SCENARIO = 3; |
| | | private static final int SAFE_READ_MANY_ERRORS = 4; |
| | | private static final int SAFE_DATA_MANY_ERRORS = 5; |
| | | |
| | | // The tracer object for the debug logger |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | private void debugInfo(String s) |
| | | { |
| | | logError(Message.raw(Category.SYNC, Severity.NOTICE, s)); |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("** TEST **" + s); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Before starting the tests configure some stuff |
| | | */ |
| | | @BeforeClass |
| | | @Override |
| | | public void setUp() throws Exception |
| | | { |
| | | super.setUp(); |
| | | |
| | | // Find a free port for the replicationServer |
| | | ServerSocket socket = TestCaseUtils.bindFreePort(); |
| | | replServerPort = socket.getLocalPort(); |
| | | socket.close(); |
| | | |
| | | // Create base dns for each tested modes |
| | | String topEntry = "dn: " + SAFE_DATA_DN + "\n" + "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(topEntry)); |
| | | topEntry = "dn: " + SAFE_READ_DN + "\n" + "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(topEntry)); |
| | | topEntry = "dn: " + NOT_ASSURED_DN + "\n" + "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(topEntry)); |
| | | } |
| | | |
| | | /** |
| | | * 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(); |
| | | assertNotNull(getEntry(entry.getDN(), 1000, true)); |
| | | } |
| | | |
| | | /** |
| | | * Creates a domain using the passed assured settings. |
| | | * Returns the matching config entry added to the config backend. |
| | | */ |
| | | private Entry createAssuredDomain(AssuredMode assuredMode, int safeDataLevel, |
| | | long assuredTimeout) throws Exception |
| | | { |
| | | String baseDn = null; |
| | | switch (assuredMode) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | baseDn = SAFE_READ_DN; |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | baseDn = SAFE_DATA_DN; |
| | | break; |
| | | default: |
| | | fail("Unexpected assured level."); |
| | | } |
| | | // Create an assured config entry ldif, matching passed assured settings |
| | | String prefixLdif = "dn: cn=" + testName + ", cn=domains, " + |
| | | SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" + |
| | | "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" + |
| | | "ds-cfg-base-dn: " + baseDn + "\n" + |
| | | "ds-cfg-replication-server: localhost:" + replServerPort + "\n" + |
| | | "ds-cfg-server-id: 1\n" + "ds-cfg-receive-status: true\n" + |
| | | // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session |
| | | // not closed by client |
| | | "ds-cfg-heartbeat-interval: 600000ms\n"; |
| | | |
| | | String configEntryLdif = null; |
| | | switch (assuredMode) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | configEntryLdif = prefixLdif + "ds-cfg-assured-type: safe-read\n" + |
| | | "ds-cfg-assured-timeout: " + assuredTimeout + "ms\n"; |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | configEntryLdif = prefixLdif + "ds-cfg-assured-type: safe-data\n" + |
| | | "ds-cfg-assured-sd-level: " + safeDataLevel + "\n" + |
| | | "ds-cfg-assured-timeout: " + assuredTimeout + "ms\n"; |
| | | break; |
| | | default: |
| | | fail("Unexpected assured level."); |
| | | } |
| | | Entry domainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif); |
| | | |
| | | // Add the config entry to create the replicated domain |
| | | DirectoryServer.getConfigHandler().addEntry(domainCfgEntry, null); |
| | | assertNotNull(DirectoryServer.getConfigEntry(domainCfgEntry.getDN()), |
| | | "Unable to add the domain config entry: " + configEntryLdif); |
| | | |
| | | return domainCfgEntry; |
| | | } |
| | | |
| | | /** |
| | | * Creates a domain without assured mode |
| | | * Returns the matching config entry added to the config backend. |
| | | */ |
| | | private Entry createNotAssuredDomain() throws Exception |
| | | { |
| | | |
| | | // Create a not assured config entry ldif |
| | | String configEntryLdif = "dn: cn=" + testName + ", cn=domains, " + |
| | | SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" + |
| | | "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" + |
| | | "ds-cfg-base-dn: " + NOT_ASSURED_DN + "\n" + |
| | | "ds-cfg-replication-server: localhost:" + replServerPort + "\n" + |
| | | "ds-cfg-server-id: 1\n" + "ds-cfg-receive-status: true\n" + |
| | | // heartbeat = 10 min so no need to emulate heartbeat in fake RS: session |
| | | // not closed by client |
| | | "ds-cfg-heartbeat-interval: 600000ms\n"; |
| | | |
| | | Entry domainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif); |
| | | |
| | | // Add the config entry to create the replicated domain |
| | | DirectoryServer.getConfigHandler().addEntry(domainCfgEntry, null); |
| | | assertNotNull(DirectoryServer.getConfigEntry(domainCfgEntry.getDN()), |
| | | "Unable to add the domain config entry: " + configEntryLdif); |
| | | |
| | | return domainCfgEntry; |
| | | } |
| | | |
| | | /** |
| | | * Removes a domain deleting the passed config entry |
| | | */ |
| | | private void removeDomain(Entry domainCfgEntry) |
| | | { |
| | | DeleteOperationBasis op; |
| | | // Delete entries |
| | | try |
| | | { |
| | | DN dn = domainCfgEntry.getDN(); |
| | | |
| | | logError(Message.raw(Category.SYNC, Severity.NOTICE, |
| | | "cleaning config entry " + dn)); |
| | | |
| | | op = new DeleteOperationBasis(connection, InternalClientConnection. |
| | | nextOperationID(), InternalClientConnection.nextMessageID(), null, |
| | | dn); |
| | | op.run(); |
| | | if ((op.getResultCode() != ResultCode.SUCCESS) && |
| | | (op.getResultCode() != ResultCode.NO_SUCH_OBJECT)) |
| | | { |
| | | fail("Deleting config entry" + dn + |
| | | " failed: " + op.getResultCode().getResultCodeName()); |
| | | } |
| | | } catch (NoSuchElementException e) |
| | | { |
| | | // done |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * The fake replication server used to emulate RS behaviour the way we want |
| | | * for assured features test |
| | | */ |
| | | private class FakeReplicationServer extends Thread |
| | | { |
| | | |
| | | private ServerSocket listenSocket; |
| | | private boolean shutdown = false; |
| | | private ProtocolSession session = null; |
| | | |
| | | // Parameters given at constructor time |
| | | private int port; |
| | | private short serverId = -1; |
| | | boolean isAssured = false; // Default value for config |
| | | AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE; // Default value for config |
| | | byte safeDataLevel = (byte) 1; // Default value for config |
| | | |
| | | // Predefined config parameters |
| | | private int degradedStatusThreshold = 5000; |
| | | |
| | | // Parameters set with received server start message |
| | | private String baseDn = null; |
| | | private long generationId = -1L; |
| | | private ServerState serverState = null; |
| | | private int windowSize = -1; |
| | | private byte groupId = (byte) -1; |
| | | private boolean sslEncryption = false; |
| | | // The scenario this RS is expecting |
| | | private int scenario = -1; |
| | | |
| | | // parameters at handshake are ok |
| | | private boolean handshakeOk = false; |
| | | // signal that the current scenario the RS must execute reached the point |
| | | // where the main code can perform test assertion |
| | | private boolean scenarioExecuted = false; |
| | | |
| | | // Constructor for RS receiving updates in SR assured mode or not assured |
| | | // The assured boolean means: |
| | | // - true: SR mode |
| | | // - false: not assured |
| | | public FakeReplicationServer(int port, short serverId, boolean assured) |
| | | { |
| | | |
| | | this.port = port; |
| | | this.serverId = serverId; |
| | | |
| | | if (assured) |
| | | { |
| | | this.isAssured = true; |
| | | this.assuredMode = AssuredMode.SAFE_READ_MODE; |
| | | } |
| | | } |
| | | |
| | | // Constructor for RS receiving updates in SD assured mode |
| | | public FakeReplicationServer(int port, short serverId, int safeDataLevel) |
| | | { |
| | | |
| | | this.port = port; |
| | | this.serverId = serverId; |
| | | |
| | | this.isAssured = true; |
| | | this.assuredMode = AssuredMode.SAFE_DATA_MODE; |
| | | this.safeDataLevel = (byte) safeDataLevel; |
| | | } |
| | | |
| | | /** |
| | | * Starts the fake RS, expecting and testing the passed scenario. |
| | | */ |
| | | public void start(int scenario) |
| | | { |
| | | |
| | | // Store expected test case |
| | | this.scenario = scenario; |
| | | |
| | | // Start listening |
| | | start(); |
| | | } |
| | | |
| | | /** |
| | | * Wait for DS connections |
| | | */ |
| | | @Override |
| | | public void run() |
| | | { |
| | | |
| | | // Create server socket |
| | | try |
| | | { |
| | | listenSocket = new ServerSocket(); |
| | | listenSocket.bind(new InetSocketAddress(port)); |
| | | } catch (IOException e) |
| | | { |
| | | fail("Fake replication server could not bind to port:" + port); |
| | | } |
| | | |
| | | Socket newSocket = null; |
| | | // Loop waiting for DS connections |
| | | while (!shutdown) |
| | | { |
| | | try |
| | | { |
| | | newSocket = listenSocket.accept(); |
| | | newSocket.setTcpNoDelay(true); |
| | | newSocket.setKeepAlive(true); |
| | | // Create client session |
| | | ReplSessionSecurity replSessionSecurity = new ReplSessionSecurity(); |
| | | session = replSessionSecurity.createServerSession(newSocket, |
| | | ReplSessionSecurity.HANDSHAKE_TIMEOUT); |
| | | if (session == null) // Error, go back to accept |
| | | { |
| | | continue; |
| | | } |
| | | // Ok, now call connection handling code + special code for the |
| | | // configured test |
| | | handleClientConnection(); |
| | | } catch (Exception e) |
| | | { |
| | | // The socket has probably been closed as part of the |
| | | // shutdown |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Shutdown the Replication Server service and all its connections. |
| | | */ |
| | | public void shutdown() |
| | | { |
| | | if (shutdown) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | shutdown = true; |
| | | |
| | | // Shutdown the listener thread |
| | | try |
| | | { |
| | | if (listenSocket != null) |
| | | { |
| | | listenSocket.close(); |
| | | } |
| | | } catch (IOException e) |
| | | { |
| | | // replication Server service is closing anyway. |
| | | } |
| | | |
| | | /* |
| | | * Shutdown any current client handling code |
| | | */ |
| | | try |
| | | { |
| | | if (session != null) |
| | | { |
| | | session.close(); |
| | | } |
| | | } catch (IOException e) |
| | | { |
| | | // ignore. |
| | | } |
| | | |
| | | try |
| | | { |
| | | join(); |
| | | } catch (InterruptedException ie) |
| | | { |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Handle the handshake processing with the connecting DS |
| | | * returns true if handshake was performed without errors |
| | | */ |
| | | private boolean performHandshake() |
| | | { |
| | | |
| | | try |
| | | { |
| | | // Receive server start |
| | | ServerStartMsg serverStartMsg = (ServerStartMsg) session.receive(); |
| | | |
| | | baseDn = serverStartMsg.getBaseDn(); |
| | | serverState = serverStartMsg.getServerState(); |
| | | generationId = serverStartMsg.getGenerationId(); |
| | | windowSize = serverStartMsg.getWindowSize(); |
| | | groupId = serverStartMsg.getGroupId(); |
| | | sslEncryption = serverStartMsg.getSSLEncryption(); |
| | | |
| | | // Send replication server start |
| | | String serverURL = ("localhost:" + port); |
| | | ReplServerStartMsg replServerStartMsg = new ReplServerStartMsg(serverId, |
| | | serverURL, baseDn, windowSize, serverState, |
| | | ProtocolVersion.getCurrentVersion(), generationId, sslEncryption, |
| | | groupId, degradedStatusThreshold); |
| | | session.publish(replServerStartMsg); |
| | | |
| | | if (!sslEncryption) |
| | | { |
| | | session.stopEncryption(); |
| | | } |
| | | |
| | | // Read start session |
| | | StartSessionMsg startSessionMsg = (StartSessionMsg) session.receive(); |
| | | |
| | | // Sanity checking for assured parameters |
| | | boolean receivedIsAssured = startSessionMsg.isAssured(); |
| | | assertEquals(receivedIsAssured, isAssured); |
| | | AssuredMode receivedAssuredMode = startSessionMsg.getAssuredMode(); |
| | | assertEquals(receivedAssuredMode, assuredMode); |
| | | byte receivedSafeDataLevel = startSessionMsg.getSafeDataLevel(); |
| | | assertEquals(receivedSafeDataLevel, safeDataLevel); |
| | | ServerStatus receivedServerStatus = startSessionMsg.getStatus(); |
| | | assertEquals(receivedServerStatus, ServerStatus.NORMAL_STATUS); |
| | | List<String> receivedReferralsURLs = startSessionMsg.getReferralsURLs(); |
| | | assertEquals(receivedReferralsURLs.size(), 0); |
| | | |
| | | debugInfo("Received start session assured parameters are ok."); |
| | | |
| | | // Send topo view |
| | | List<RSInfo> rsList = new ArrayList<RSInfo>(); |
| | | RSInfo rsInfo = new RSInfo(serverId, generationId, groupId); |
| | | rsList.add(rsInfo); |
| | | TopologyMsg topologyMsg = new TopologyMsg(new ArrayList<DSInfo>(), |
| | | rsList); |
| | | session.publish(topologyMsg); |
| | | |
| | | } catch (IOException e) |
| | | { |
| | | // Probably un-connection of DS looking for best server |
| | | return false; |
| | | } catch (Exception e) |
| | | { |
| | | fail("Unexpected exception in fake replication server handshake " + |
| | | "processing: " + e); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Tells if the received handshake parameters regarding assured config were |
| | | * ok and handshake phase is terminated. |
| | | */ |
| | | public boolean isHandshakeOk() |
| | | { |
| | | |
| | | return handshakeOk; |
| | | } |
| | | |
| | | /** |
| | | * Tells the main code that the fake RS executed enough of the expected |
| | | * scenario and can perform test assertion |
| | | */ |
| | | public boolean isScenarioExecuted() |
| | | { |
| | | |
| | | return scenarioExecuted; |
| | | } |
| | | |
| | | /** |
| | | * Handle client connection then call code specific to configured test |
| | | */ |
| | | private void handleClientConnection() |
| | | { |
| | | |
| | | // Handle DS connexion |
| | | if (!performHandshake()) |
| | | { |
| | | try |
| | | { |
| | | session.close(); |
| | | } catch (IOException e) |
| | | { |
| | | // nothing to do |
| | | } |
| | | return; |
| | | } |
| | | // If we come here, assured parameters sent by DS are as expected and |
| | | // handshake phase is terminated |
| | | handshakeOk = true; |
| | | |
| | | // Now execute the requested scenario |
| | | switch (scenario) |
| | | { |
| | | case NOT_ASSURED_SCENARIO: |
| | | executeNotAssuredScenario(); |
| | | break; |
| | | case TIMEOUT_SCENARIO: |
| | | executeTimeoutScenario(); |
| | | break; |
| | | case NO_TIMEOUT_SCENARIO: |
| | | executeNoTimeoutScenario(); |
| | | break; |
| | | case SAFE_READ_MANY_ERRORS: |
| | | executeSafeReadManyErrorsScenario(); |
| | | break; |
| | | case SAFE_DATA_MANY_ERRORS: |
| | | executeSafeDataManyErrorsScenario(); |
| | | break; |
| | | default: |
| | | fail("Unknown scenario: " + scenario); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Read the coming update and check parameters are not assured |
| | | */ |
| | | private void executeNotAssuredScenario() |
| | | { |
| | | |
| | | try |
| | | { |
| | | UpdateMsg updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | scenarioExecuted = true; |
| | | } catch (Exception e) |
| | | { |
| | | fail("Unexpected exception in fake replication server executeNotAssuredScenario " + |
| | | "processing: " + e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Read the coming update and make the client time out by not sending back |
| | | * the ack |
| | | */ |
| | | private void executeTimeoutScenario() |
| | | { |
| | | |
| | | try |
| | | { |
| | | UpdateMsg updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | scenarioExecuted = true; |
| | | |
| | | // We do not send back an ack and the client code is expected to be |
| | | // blocked at least for the programmed timeout time. |
| | | |
| | | } catch (Exception e) |
| | | { |
| | | fail("Unexpected exception in fake replication server executeTimeoutScenario " + |
| | | "processing: " + e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Read the coming update, sleep some time then send back an ack |
| | | */ |
| | | private void executeNoTimeoutScenario() |
| | | { |
| | | |
| | | try |
| | | { |
| | | UpdateMsg updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // Sleep before sending back the ack |
| | | sleep(NO_TIMEOUT_RS_SLEEP_TIME); |
| | | |
| | | // Send the ack without errors |
| | | AckMsg ackMsg = new AckMsg(updateMsg.getChangeNumber()); |
| | | session.publish(ackMsg); |
| | | |
| | | scenarioExecuted = true; |
| | | |
| | | } catch (Exception e) |
| | | { |
| | | fail("Unexpected exception in fake replication server executeNoTimeoutScenario " + |
| | | "processing: " + e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check that received update assured parameters are as defined at RS start |
| | | */ |
| | | private void checkUpdateAssuredParameters(UpdateMsg updateMsg) |
| | | { |
| | | assertEquals(updateMsg.isAssured(), isAssured); |
| | | assertEquals(updateMsg.getAssuredMode(), assuredMode); |
| | | assertEquals(updateMsg.getSafeDataLevel(), safeDataLevel); |
| | | debugInfo("Received update assured parameters are ok."); |
| | | } |
| | | |
| | | /** |
| | | * Read the coming seaf read mode updates and send back acks with errors |
| | | */ |
| | | private void executeSafeReadManyErrorsScenario() |
| | | { |
| | | |
| | | try |
| | | { |
| | | // Read first update |
| | | UpdateMsg updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // Sleep before sending back the ack |
| | | sleep(NO_TIMEOUT_RS_SLEEP_TIME); |
| | | |
| | | // Send an ack with errors: |
| | | // - replay error |
| | | // - server 10 error, server 20 error |
| | | List<Short> serversInError = new ArrayList<Short>(); |
| | | serversInError.add((short)10); |
| | | serversInError.add((short)20); |
| | | AckMsg ackMsg = new AckMsg(updateMsg.getChangeNumber(), false, false, true, serversInError); |
| | | session.publish(ackMsg); |
| | | |
| | | // Read second update |
| | | updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // Sleep before sending back the ack |
| | | sleep(NO_TIMEOUT_RS_SLEEP_TIME); |
| | | |
| | | // Send an ack with errors: |
| | | // - timeout error |
| | | // - wrong status error |
| | | // - replay error |
| | | // - server 10 error, server 20 error, server 30 error |
| | | serversInError = new ArrayList<Short>(); |
| | | serversInError.add((short)10); |
| | | serversInError.add((short)20); |
| | | serversInError.add((short)30); |
| | | ackMsg = new AckMsg(updateMsg.getChangeNumber(), true, true, true, serversInError); |
| | | session.publish(ackMsg); |
| | | |
| | | // Read third update |
| | | updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // let timeout occur |
| | | |
| | | scenarioExecuted = true; |
| | | |
| | | } catch (Exception e) |
| | | { |
| | | fail("Unexpected exception in fake replication server executeSafeReadManyErrorsScenario " + |
| | | "processing: " + e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Read the coming seaf data mode updates and send back acks with errors |
| | | */ |
| | | private void executeSafeDataManyErrorsScenario() |
| | | { |
| | | |
| | | try |
| | | { |
| | | // Read first update |
| | | UpdateMsg updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // Sleep before sending back the ack |
| | | sleep(NO_TIMEOUT_RS_SLEEP_TIME); |
| | | |
| | | // Send an ack with errors: |
| | | // - timeout error |
| | | // - server 10 error |
| | | List<Short> serversInError = new ArrayList<Short>(); |
| | | serversInError.add((short)10); |
| | | AckMsg ackMsg = new AckMsg(updateMsg.getChangeNumber(), true, false, false, serversInError); |
| | | session.publish(ackMsg); |
| | | |
| | | // Read second update |
| | | updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // Sleep before sending back the ack |
| | | sleep(NO_TIMEOUT_RS_SLEEP_TIME); |
| | | |
| | | // Send an ack with errors: |
| | | // - timeout error |
| | | // - server 10 error, server 20 error |
| | | serversInError = new ArrayList<Short>(); |
| | | serversInError.add((short)10); |
| | | serversInError.add((short)20); |
| | | ackMsg = new AckMsg(updateMsg.getChangeNumber(), true, false, false, serversInError); |
| | | session.publish(ackMsg); |
| | | |
| | | // Read third update |
| | | updateMsg = (UpdateMsg) session.receive(); |
| | | checkUpdateAssuredParameters(updateMsg); |
| | | |
| | | // let timeout occur |
| | | |
| | | scenarioExecuted = true; |
| | | |
| | | } catch (Exception e) |
| | | { |
| | | fail("Unexpected exception in fake replication server executeSafeDataManyErrorsScenario " + |
| | | "processing: " + e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Sleep a while |
| | | */ |
| | | private void sleep(long time) |
| | | { |
| | | try |
| | | { |
| | | Thread.sleep(time); |
| | | } catch (InterruptedException ex) |
| | | { |
| | | fail("Error sleeping " + ex); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Tests that a DS performing a modification in safe data mode waits for |
| | | * the ack of the RS for the configured timeout time, then times out. |
| | | */ |
| | | @Test |
| | | public void testSafeDataModeTimeout() throws Exception |
| | | { |
| | | |
| | | int TIMEOUT = 5000; |
| | | String testcase = "testSafeDataModeTimeout"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting clients in safe data assured mode with |
| | | // safe data level 2 |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | 1); |
| | | replicationServer.start(TIMEOUT_SCENARIO); |
| | | |
| | | // Create a safe data assured domain |
| | | safeDataDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_DATA_MODE, 1, |
| | | TIMEOUT); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make an LDAP update (add an entry) |
| | | long startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | String entry = "dn: ou=assured-sd-timeout-entry," + SAFE_DATA_DN + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will not send back an ack so we expect |
| | | // the add entry code (LDAP client code emulation) to be blocked for the |
| | | // timeout value at least. If the time we have slept is lower, timeout |
| | | // handling code is not working... |
| | | long endTime = System.currentTimeMillis(); |
| | | assertTrue((endTime - startTime) >= TIMEOUT); |
| | | assertTrue(replicationServer.isScenarioExecuted()); |
| | | |
| | | // Check monitoring values |
| | | DN baseDn = DN.decode(SAFE_DATA_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 1); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | // errors by server list for sd mode should be [[rsId:1]] |
| | | assertEquals(errorsByServer.size(), 1); |
| | | Integer nError = errorsByServer.get((short)RS_SERVER_ID); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Tests that a DS performing a modification in safe read mode waits for |
| | | * the ack of the RS for the configured timeout time, then times out. |
| | | */ |
| | | @Test |
| | | public void testSafeReadModeTimeout() throws Exception |
| | | { |
| | | |
| | | int TIMEOUT = 5000; |
| | | String testcase = "testSafeReadModeTimeout"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting clients in safe read assured mode |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | true); |
| | | replicationServer.start(TIMEOUT_SCENARIO); |
| | | |
| | | // Create a safe read assured domain |
| | | safeReadDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_READ_MODE, 0, |
| | | TIMEOUT); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make an LDAP update (add an entry) |
| | | long startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | String entry = "dn: ou=assured-sr-timeout-entry," + SAFE_READ_DN + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will not send back an ack so we expect |
| | | // the add entry code (LDAP client code emulation) to be blocked for the |
| | | // timeout value at least. If the time we have slept is lower, timeout |
| | | // handling code is not working... |
| | | long endTime = System.currentTimeMillis(); |
| | | assertTrue((endTime - startTime) >= TIMEOUT); |
| | | assertTrue(replicationServer.isScenarioExecuted()); |
| | | |
| | | // Check monitoring values |
| | | DN baseDn = DN.decode(SAFE_READ_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | // errors by server list for sr mode should be [[rsId:1]] |
| | | assertEquals(errorsByServer.size(), 1); |
| | | Integer nError = errorsByServer.get((short)RS_SERVER_ID); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Tests parameters sent in session handshake an updates, when not using |
| | | * assured replication |
| | | */ |
| | | @Test |
| | | public void testNotAssuredSession() throws Exception |
| | | { |
| | | |
| | | String testcase = "testNotAssuredSession"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting not assured clients |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | false); |
| | | replicationServer.start(NOT_ASSURED_SCENARIO); |
| | | |
| | | // Create a not assured domain |
| | | notAssuredDomainCfgEntry = createNotAssuredDomain(); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make an LDAP update (add an entry) |
| | | String entry = "dn: ou=not-assured-entry," + NOT_ASSURED_DN + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // Wait for entry received by RS |
| | | waitForScenarioExecutedOnRs(testcase, replicationServer); |
| | | |
| | | // No more test to do here |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Wait for connection to the fake replication server or times out with error |
| | | * after some seconds |
| | | */ |
| | | private void waitForConnectionToRs(String testCase, FakeReplicationServer rs) |
| | | { |
| | | int nsec = -1; |
| | | do |
| | | { |
| | | nsec++; |
| | | if (nsec == 10) // 10 seconds timeout |
| | | fail(testCase + ": timeout waiting for domain connection to fake RS after " + nsec + " seconds."); |
| | | sleep(1000); |
| | | } while (!rs.isHandshakeOk()); |
| | | } |
| | | |
| | | /** |
| | | * Wait for the scenario to be executed by the fake replication server or |
| | | * times out with error after some seconds |
| | | */ |
| | | private void waitForScenarioExecutedOnRs(String testCase, FakeReplicationServer rs) |
| | | { |
| | | int nsec = -1; |
| | | do |
| | | { |
| | | nsec++; |
| | | if (nsec == 10) // 10 seconds timeout |
| | | fail(testCase + ": timeout waiting for scenario to be exectued on fake RS after " + nsec + " seconds."); |
| | | sleep(1000); |
| | | } while (!rs.isScenarioExecuted()); |
| | | } |
| | | |
| | | private void endTest() |
| | | { |
| | | if (replicationServer != null) |
| | | { |
| | | replicationServer.shutdown(); |
| | | } |
| | | |
| | | if (safeDataDomainCfgEntry != null) |
| | | { |
| | | removeDomain(safeDataDomainCfgEntry); |
| | | } |
| | | |
| | | if (safeReadDomainCfgEntry != null) |
| | | { |
| | | removeDomain(safeReadDomainCfgEntry); |
| | | } |
| | | |
| | | if (notAssuredDomainCfgEntry != null) |
| | | { |
| | | removeDomain(notAssuredDomainCfgEntry); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Tests that a DS performing a modification in safe data mode receives the RS |
| | | * ack and does not return before returning it. |
| | | */ |
| | | @Test |
| | | public void testSafeDataModeAck() throws Exception |
| | | { |
| | | |
| | | int TIMEOUT = 5000; |
| | | String testcase = "testSafeDataModeAck"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting clients in safe data assured mode with |
| | | // safe data level 2 |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | 2); |
| | | replicationServer.start(NO_TIMEOUT_SCENARIO); |
| | | |
| | | // Create a safe data assured domain |
| | | safeDataDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_DATA_MODE, 2, |
| | | TIMEOUT); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make an LDAP update (add an entry) |
| | | long startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | String entry = "dn: ou=assured-sd-no-timeout-entry," + SAFE_DATA_DN + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME |
| | | // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked |
| | | // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value. |
| | | long endTime = System.currentTimeMillis(); |
| | | long callTime = endTime - startTime; |
| | | assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT)); |
| | | assertTrue(replicationServer.isScenarioExecuted()); |
| | | |
| | | // Check monitoring values |
| | | DN baseDn = DN.decode(SAFE_DATA_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Tests that a DS performing a modification in safe read mode receives the RS |
| | | * ack and does not return before returning it. |
| | | */ |
| | | @Test |
| | | public void testSafeReadModeAck() throws Exception |
| | | { |
| | | |
| | | int TIMEOUT = 5000; |
| | | String testcase = "testSafeReadModeAck"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting clients in safe read assured mode |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | true); |
| | | replicationServer.start(NO_TIMEOUT_SCENARIO); |
| | | |
| | | // Create a safe read assured domain |
| | | safeReadDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_READ_MODE, 0, |
| | | TIMEOUT); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make an LDAP update (add an entry) |
| | | long startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | String entry = "dn: ou=assured-sr-no-timeout-entry," + SAFE_READ_DN + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME |
| | | // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked |
| | | // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value. |
| | | long endTime = System.currentTimeMillis(); |
| | | long callTime = endTime - startTime; |
| | | assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT)); |
| | | assertTrue(replicationServer.isScenarioExecuted()); |
| | | |
| | | // Check monitoring values |
| | | DN baseDn = DN.decode(SAFE_READ_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * DS performs many successive modifications in safe data mode and receives RS |
| | | * acks with various errors. Check for monitoring right errors |
| | | */ |
| | | @Test |
| | | public void testSafeDataManyErrors() throws Exception |
| | | { |
| | | |
| | | int TIMEOUT = 5000; |
| | | String testcase = "testSafeDataManyErrors"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting clients in safe data assured mode with |
| | | // safe data level 3 |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | 3); |
| | | replicationServer.start(SAFE_DATA_MANY_ERRORS); |
| | | |
| | | // Create a safe data assured domain |
| | | safeDataDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_DATA_MODE, 3, |
| | | TIMEOUT); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make a first LDAP update (add an entry) |
| | | long startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | String entryDn = "ou=assured-sd-many-errors-entry," + SAFE_DATA_DN; |
| | | String entry = "dn: " + entryDn + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME |
| | | // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked |
| | | // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value. |
| | | long endTime = System.currentTimeMillis(); |
| | | long callTime = endTime - startTime; |
| | | assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT)); |
| | | |
| | | // Check monitoring values |
| | | // The expected ack for the first update is: |
| | | // - timeout error |
| | | // - server 10 error |
| | | DN baseDn = DN.decode(SAFE_DATA_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 1); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | // errors by server list for sd mode should be [[10:1]] |
| | | assertEquals(errorsByServer.size(), 1); |
| | | Integer nError = errorsByServer.get((short)10); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | |
| | | // Make a second LDAP update (delete the entry) |
| | | startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | deleteEntry(entryDn); |
| | | |
| | | // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME |
| | | // seconds, so we expect the delete entry code (LDAP client code emulation) to be blocked |
| | | // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value. |
| | | endTime = System.currentTimeMillis(); |
| | | callTime = endTime - startTime; |
| | | assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT)); |
| | | |
| | | // Check monitoring values |
| | | // The expected ack for the second update is: |
| | | // - timeout error |
| | | // - server 10 error, server 20 error |
| | | baseDn = DN.decode(SAFE_DATA_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 2); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 2); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | // errors by server list for sd mode should be [[10:2],[20:1]] |
| | | assertEquals(errorsByServer.size(), 2); |
| | | nError = errorsByServer.get((short)10); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 2); |
| | | nError = errorsByServer.get((short)20); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | |
| | | // Make a third LDAP update (re-add the entry) |
| | | startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will not send back an ack so we expect |
| | | // the add entry code (LDAP client code emulation) to be blocked for the |
| | | // timeout value at least. If the time we have slept is lower, timeout |
| | | // handling code is not working... |
| | | endTime = System.currentTimeMillis(); |
| | | assertTrue((endTime - startTime) >= TIMEOUT); |
| | | assertTrue(replicationServer.isScenarioExecuted()); |
| | | |
| | | // Check monitoring values |
| | | // No ack should have comen back, so timeout incremented (flag and error for rs) |
| | | baseDn = DN.decode(SAFE_DATA_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 3); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 3); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | // errors by server list for sd mode should be [[10:2],[20:1],[rsId:1]] |
| | | assertEquals(errorsByServer.size(), 3); |
| | | nError = errorsByServer.get((short)10); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 2); |
| | | nError = errorsByServer.get((short)20); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | nError = errorsByServer.get((short)RS_SERVER_ID); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * DS performs many successive modifications in safe read mode and receives RS |
| | | * acks with various errors. Check for monitoring right errors |
| | | */ |
| | | @Test |
| | | public void testSafeReadManyErrors() throws Exception |
| | | { |
| | | |
| | | int TIMEOUT = 5000; |
| | | String testcase = "testSafeReadManyErrors"; |
| | | try |
| | | { |
| | | // Create and start a RS expecting clients in safe read assured mode |
| | | replicationServer = new FakeReplicationServer(replServerPort, RS_SERVER_ID, |
| | | true); |
| | | replicationServer.start(SAFE_READ_MANY_ERRORS); |
| | | |
| | | // Create a safe read assured domain |
| | | safeReadDomainCfgEntry = createAssuredDomain(AssuredMode.SAFE_READ_MODE, 0, |
| | | TIMEOUT); |
| | | // Wait for connection of domain to RS |
| | | waitForConnectionToRs(testcase, replicationServer); |
| | | |
| | | // Make a first LDAP update (add an entry) |
| | | long startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | String entryDn = "ou=assured-sr-many-errors-entry," + SAFE_READ_DN; |
| | | String entry = "dn: " + entryDn + "\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: organizationalUnit\n"; |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME |
| | | // seconds, so we expect the add entry code (LDAP client code emulation) to be blocked |
| | | // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value. |
| | | long endTime = System.currentTimeMillis(); |
| | | long callTime = endTime - startTime; |
| | | assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT)); |
| | | |
| | | // Check monitoring values |
| | | // The expected ack for the first update is: |
| | | // - replay error |
| | | // - server 10 error, server 20 error |
| | | DN baseDn = DN.decode(SAFE_READ_DN); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 1); |
| | | Map<Short, Integer> errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | // errors by server list for sr mode should be [[10:1],[20:1]] |
| | | assertEquals(errorsByServer.size(), 2); |
| | | Integer nError = errorsByServer.get((short)10); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | nError = errorsByServer.get((short)20); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | |
| | | // Make a second LDAP update (delete the entry) |
| | | startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | deleteEntry(entryDn); |
| | | |
| | | // In this scenario, the fake RS will send back an ack after NO_TIMEOUT_RS_SLEEP_TIME |
| | | // seconds, so we expect the delete entry code (LDAP client code emulation) to be blocked |
| | | // for more than NO_TIMEOUT_RS_SLEEP_TIME seconds but no more than the timeout value. |
| | | endTime = System.currentTimeMillis(); |
| | | callTime = endTime - startTime; |
| | | assertTrue( (callTime >= NO_TIMEOUT_RS_SLEEP_TIME) && (callTime <= TIMEOUT)); |
| | | |
| | | // Check monitoring values |
| | | // The expected ack for the second update is: |
| | | // - timeout error |
| | | // - wrong status error |
| | | // - replay error |
| | | // - server 10 error, server 20 error, server 30 error |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 2); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 2); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 2); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | // errors by server list for sr mode should be [[10:2],[20:2],[30:1]] |
| | | assertEquals(errorsByServer.size(), 3); |
| | | nError = errorsByServer.get((short)10); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 2); |
| | | nError = errorsByServer.get((short)20); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 2); |
| | | nError = errorsByServer.get((short)30); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | |
| | | // Make a third LDAP update (re-add the entry) |
| | | startTime = System.currentTimeMillis(); // Time the update has been initiated |
| | | addEntry(TestCaseUtils.entryFromLdifString(entry)); |
| | | |
| | | // In this scenario, the fake RS will not send back an ack so we expect |
| | | // the add entry code (LDAP client code emulation) to be blocked for the |
| | | // timeout value at least. If the time we have slept is lower, timeout |
| | | // handling code is not working... |
| | | endTime = System.currentTimeMillis(); |
| | | assertTrue((endTime - startTime) >= TIMEOUT); |
| | | assertTrue(replicationServer.isScenarioExecuted()); |
| | | |
| | | // Check monitoring values |
| | | // No ack should have comen back, so timeout incremented (flag and error for rs) |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-sent-updates"), 3); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-not-acknowledged-updates"), 3); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-timeout-updates"), 2); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-wrong-status-updates"), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sr-replay-error-updates"), 2); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_READ_MODE); |
| | | // errors by server list for sr mode should be [[10:2],[20:2],[30:1],[rsId:1]] |
| | | assertEquals(errorsByServer.size(), 4); |
| | | nError = errorsByServer.get((short)10); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 2); |
| | | nError = errorsByServer.get((short)20); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 2); |
| | | nError = errorsByServer.get((short)30); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | nError = errorsByServer.get((short)RS_SERVER_ID); |
| | | assertNotNull(nError); |
| | | assertEquals(nError.intValue(), 1); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-sent-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-acknowledged-updates"), 0); |
| | | assertEquals(getMonitorAttrValue(baseDn, "assured-sd-timeout-updates"), 0); |
| | | errorsByServer = getErrorsByServers(baseDn, AssuredMode.SAFE_DATA_MODE); |
| | | assertTrue(errorsByServer.isEmpty()); |
| | | |
| | | } finally |
| | | { |
| | | endTest(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Delete an entry from the database |
| | | */ |
| | | private void deleteEntry(String dn) throws Exception |
| | | { |
| | | DN realDn = DN.decode(dn); |
| | | DeleteOperationBasis delOp = new DeleteOperationBasis(connection, |
| | | InternalClientConnection.nextOperationID(), InternalClientConnection. |
| | | nextMessageID(), null, realDn); |
| | | delOp.setInternalOperation(true); |
| | | delOp.run(); |
| | | assertNull(DirectoryServer.getEntry(realDn)); |
| | | } |
| | | |
| | | /** |
| | | * Get the errors by servers from cn=monitor, according to the requested base dn |
| | | * and the requested mode |
| | | * This corresponds to the values for multi valued attributes: |
| | | * - assured-sr-server-not-acknowledged-updates in SR mode |
| | | * - assured-sd-server-timeout-updates in SD mode |
| | | */ |
| | | protected Map<Short,Integer> getErrorsByServers(DN baseDn, |
| | | AssuredMode assuredMode) throws Exception |
| | | { |
| | | /* |
| | | * Find monitoring entry for requested base DN |
| | | */ |
| | | String monitorFilter = |
| | | "(&(cn=replication Domain*)(base-dn=" + baseDn + "))"; |
| | | |
| | | InternalSearchOperation op; |
| | | int count = 0; |
| | | do |
| | | { |
| | | if (count++>0) |
| | | Thread.sleep(100); |
| | | op = connection.processSearch( |
| | | ByteStringFactory.create("cn=monitor"), |
| | | SearchScope.SINGLE_LEVEL, |
| | | LDAPFilter.decode(monitorFilter)); |
| | | } |
| | | while (op.getSearchEntries().isEmpty() && (count<100)); |
| | | if (op.getSearchEntries().isEmpty()) |
| | | throw new Exception("Could not read monitoring information"); |
| | | |
| | | SearchResultEntry entry = op.getSearchEntries().getFirst(); |
| | | |
| | | if (entry == null) |
| | | throw new Exception("Could not find monitoring entry"); |
| | | |
| | | /* |
| | | * Find the multi valued attribute matching the requested assured mode |
| | | */ |
| | | String assuredAttr = null; |
| | | switch(assuredMode) |
| | | { |
| | | case SAFE_READ_MODE: |
| | | assuredAttr = "assured-sr-server-not-acknowledged-updates"; |
| | | break; |
| | | case SAFE_DATA_MODE: |
| | | assuredAttr = "assured-sd-server-timeout-updates"; |
| | | break; |
| | | default: |
| | | throw new Exception("Unknown assured type"); |
| | | } |
| | | |
| | | List<Attribute> attrs = entry.getAttribute(assuredAttr); |
| | | |
| | | Map<Short,Integer> resultMap = new HashMap<Short,Integer>(); |
| | | if ( (attrs == null) || (attrs.isEmpty()) ) |
| | | return resultMap; // Empty map |
| | | |
| | | Attribute attr = attrs.get(0); |
| | | Iterator<AttributeValue> attValIt = attr.iterator(); |
| | | // Parse and store values |
| | | while (attValIt.hasNext()) |
| | | { |
| | | String srvStr = attValIt.next().getStringValue(); |
| | | StringTokenizer strtok = new StringTokenizer(srvStr, ":"); |
| | | String token = strtok.nextToken(); |
| | | if (token != null) |
| | | { |
| | | Short serverId = Short.valueOf(token); |
| | | token = strtok.nextToken(); |
| | | if (token != null) |
| | | { |
| | | Integer nerrors = Integer.valueOf(token); |
| | | resultMap.put(serverId, nerrors); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return resultMap; |
| | | } |
| | | } |
| | | |
| | |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import java.util.HashMap; |
| | | import static org.opends.server.replication.plugin.ReplicationBroker.*; |
| | | import static org.opends.server.replication.service.ReplicationBroker.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | import static org.opends.server.loggers.debug.DebugLogger.getTracer; |
| | |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.plugin.ReplicationBroker.ServerInfo; |
| | | import org.opends.server.types.DN; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | |
| | | final String WINNER = "winner"; |
| | | |
| | | // Create my state |
| | | ServerState mySt = new ServerState(); |
| | | ServerState mySt = new ServerState(); |
| | | ChangeNumber cn = new ChangeNumber(2L, 0, myId2); // Should not be used inside algo |
| | | mySt.update(cn); |
| | | cn = new ChangeNumber(3L, 0, myId3); // Should not be used inside algo |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | short myId1 = 1; |
| | | short myId2 = 2; |
| | | short myId3 = 3; |
| | | |
| | | |
| | | // definitions for server names |
| | | final String WINNER = "winner"; |
| | | |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(LOOSER1, new ServerInfo(aState, (byte)2)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Test with 2 replication servers, none of them from our group id. |
| | | * |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)2)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(LOOSER2, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Test with 3 replication servers, up to date, but 2 different group ids. |
| | | * |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Test with 3 replication servers, late. |
| | | * |
| | |
| | | rsInfos.put(LOOSER2, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Test with 6 replication servers, some up, some late, one null |
| | | * |
| | |
| | | // definitions for server ids |
| | | short myId1 = 1; |
| | | short myId2 = 2; |
| | | short myId3 = 3; |
| | | |
| | | short myId3 = 3; |
| | | |
| | | // definitions for server names |
| | | final String WINNER = "winner"; |
| | | final String LOOSER1 = "looser1"; |
| | |
| | | cn = new ChangeNumber(10L, 0, myId3); |
| | | aState.update(cn); |
| | | rsInfos.put(LOOSER3, new ServerInfo(aState, (byte)1)); |
| | | |
| | | |
| | | // State for server 4 |
| | | aState = new ServerState(); |
| | | cn = new ChangeNumber(6L, 0, myId1); |
| | |
| | | cn = new ChangeNumber(8L, 0, myId3); |
| | | aState.update(cn); |
| | | rsInfos.put(WINNER, new ServerInfo(aState, (byte)1)); |
| | | |
| | | |
| | | // State for server 5 (null one for our serverid) |
| | | aState = new ServerState(); |
| | | cn = new ChangeNumber(5L, 0, myId2); |
| | |
| | | cn = new ChangeNumber(5L, 0, myId3); |
| | | aState.update(cn); |
| | | rsInfos.put(LOOSER4, new ServerInfo(aState, (byte)1)); |
| | | |
| | | |
| | | // State for server 6 |
| | | aState = new ServerState(); |
| | | cn = new ChangeNumber(5L, 0, myId1); |
| | |
| | | rsInfos.put(LOOSER5, new ServerInfo(aState, (byte)1)); |
| | | |
| | | String bestServer = |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, new DN(), (byte)1); |
| | | computeBestReplicationServer(mySt, rsInfos, myId1, " ", (byte)1); |
| | | |
| | | assertEquals(bestServer, WINNER, "Wrong best replication server."); |
| | | } |
| | |
| | | import java.net.ServerSocket; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | import static org.opends.server.replication.plugin.ReplicationBroker.*; |
| | | |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | import static org.opends.server.loggers.debug.DebugLogger.getTracer; |
| | |
| | | private int rs1Port = -1; |
| | | private int rs2Port = -1; |
| | | private int rs3Port = -1; |
| | | private ReplicationDomain rd1 = null; |
| | | private ReplicationDomain rd2 = null; |
| | | private LDAPReplicationDomain rd1 = null; |
| | | private LDAPReplicationDomain rd2 = null; |
| | | private ReplicationServer rs1 = null; |
| | | private ReplicationServer rs2 = null; |
| | | private ReplicationServer rs3 = null; |
| | |
| | | rs3Port = -1; |
| | | } |
| | | |
| | | private void sleep(long time) |
| | | { |
| | | try |
| | | { |
| | | Thread.sleep(time); |
| | | } catch (InterruptedException ex) |
| | | { |
| | | fail("Error sleeping " + stackTraceToSingleLineString(ex)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check connection of the provided replication domain to the provided |
| | |
| | | { |
| | | |
| | | int rsPort = -1; |
| | | ReplicationDomain rd = null; |
| | | LDAPReplicationDomain rd = null; |
| | | switch (dsId) |
| | | { |
| | | case DS1_ID: |
| | |
| | | private SortedSet<String> createRSListForTestCase(String testCase) |
| | | { |
| | | SortedSet<String> replServers = new TreeSet<String>(); |
| | | |
| | | |
| | | if (testCase.equals("testRSWithSameGroupIds")) |
| | | { |
| | | // 2 servers used for this test case. |
| | |
| | | /** |
| | | * Creates a new ReplicationDomain. |
| | | */ |
| | | private ReplicationDomain createReplicationDomain(short serverId, |
| | | private LDAPReplicationDomain createReplicationDomain(short serverId, |
| | | int groupId, String testCase) |
| | | { |
| | | |
| | |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | DomainFakeCfg domainConf = |
| | | new DomainFakeCfg(baseDn, serverId, replServers, groupId); |
| | | ReplicationDomain replicationDomain = |
| | | LDAPReplicationDomain replicationDomain = |
| | | MultimasterReplication.createNewDomain(domainConf); |
| | | replicationDomain.start(); |
| | | return replicationDomain; |
| | |
| | | throws Exception |
| | | { |
| | | // Creates a rule |
| | | HistoricalCsnOrderingMatchingRule r = |
| | | HistoricalCsnOrderingMatchingRule r = |
| | | new HistoricalCsnOrderingMatchingRule(); |
| | | |
| | | ChangeNumber del1 = new ChangeNumber(1, (short) 0, (short) 1); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Test that we can retrieve the entries that were missed by |
| | | * Test that we can retrieve the entries that were missed by |
| | | * a replication server and can re-build operations from the historical |
| | | * informations. |
| | | */ |
| | |
| | | logError(Message.raw(Category.SYNC, Severity.INFORMATION, |
| | | "First historical value:" + histValue)); |
| | | |
| | | // Perform a 2nd modification to update the hist attribute with |
| | | // Perform a 2nd modification to update the hist attribute with |
| | | // a second value |
| | | resultCode = TestCaseUtils.applyModifications(false, |
| | | "dn: cn=test1," + baseDn.toString(), |
| | |
| | | |
| | | // Retrieves the entries that have changed since the first modification |
| | | InternalSearchOperation op = |
| | | ReplicationBroker.searchForChangedEntries(baseDn, fromChangeNumber, null); |
| | | LDAPReplicationDomain.searchForChangedEntries( |
| | | baseDn, fromChangeNumber, null); |
| | | |
| | | // The expected result is one entry .. the one previously modified |
| | | assertEquals(op.getResultCode(), ResultCode.SUCCESS); |
| | |
| | | updatesCnt++; |
| | | } |
| | | } |
| | | assertTrue(updatesCnt == 3); |
| | | assertTrue(updatesCnt == 3); |
| | | } |
| | | } |
| | |
| | | |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.ModifyMsg; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.tools.LDAPModify; |
| | |
| | | ModifyMsg modMsg = new ModifyMsg(changeNum, dn, mods, entryuuid); |
| | | broker.publish(modMsg); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Test that historical information is correctly added when performaing ADD, |
| | | * MOD and MODDN operations. |
| | |
| | | |
| | | for (FakeOperation fake : ops) |
| | | { |
| | | UpdateMsg msg = (UpdateMsg) fake.generateMessage(); |
| | | LDAPUpdateMsg msg = (LDAPUpdateMsg) fake.generateMessage(); |
| | | AbstractOperation op = |
| | | msg.createOperation(InternalClientConnection.getRootConnection()); |
| | | op.setInternalOperation(true); |
| | |
| | | AddMsg addmsg = addOp.generateMessage(); |
| | | assertTrue(dn1.equals(DN.decode(addmsg.getDn()))); |
| | | assertTrue(addmsg.getUniqueId().equals(Historical.getEntryUuid(entry))); |
| | | String parentId = ReplicationDomain.findEntryId(dn1.getParent()); |
| | | String parentId = LDAPReplicationDomain.findEntryId(dn1.getParent()); |
| | | assertTrue(addmsg.getParentUid().equals(parentId)); |
| | | addmsg.createOperation(InternalClientConnection.getRootConnection()); |
| | | } else |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | assertEquals(count, assertCount); |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileNotFoundException; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | |
| | | import java.net.ServerSocket; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | import java.util.UUID; |
| | | |
| | | import static org.testng.Assert.*; |
| | | |
| | |
| | | @Test() |
| | | public void noUpdateIsolationPolicyTest() throws Exception |
| | | { |
| | | ReplicationDomain domain = null; |
| | | LDAPReplicationDomain domain = null; |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | SynchronizationProvider replicationPlugin = null; |
| | | short serverId = 1; |
| | |
| | | op = conn.processModify(baseDn, generatemods("description", "test")); |
| | | |
| | | // check that the operation was successful. |
| | | assertEquals(op.getResultCode(), ResultCode.SUCCESS, |
| | | assertEquals(op.getResultCode(), ResultCode.SUCCESS, |
| | | op.getAdditionalLogMessage().toString()); |
| | | } |
| | | finally |
| | |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | | import org.opends.server.replication.protocol.LDAPUpdateMsg; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedList; |
| | |
| | | import org.opends.server.replication.plugin.Historical; |
| | | import org.opends.server.replication.protocol.ModifyContext; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeBuilder; |
| | | import org.opends.server.types.AttributeType; |
| | |
| | | assertTrue(new FakeOperationComparator().compare(fk, fk) == 0); |
| | | assertTrue(new FakeOperationComparator().compare(null , fk) < 0); |
| | | ReplicationMsg generatedMsg = fk.generateMessage() ; |
| | | if (generatedMsg instanceof UpdateMsg) |
| | | if (generatedMsg instanceof LDAPUpdateMsg) |
| | | { |
| | | UpdateMsg new_name = (UpdateMsg) generatedMsg; |
| | | LDAPUpdateMsg new_name = (LDAPUpdateMsg) generatedMsg; |
| | | assertEquals(new_name.getUniqueId(),uuid); |
| | | |
| | | } |
| | |
| | | |
| | | import static org.testng.Assert.assertEquals; |
| | | |
| | | import org.opends.server.replication.common.ServerState; |
| | | |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | |
| | | * 2 ChangeNumbers have been saved in this new PersistentServerState. |
| | | */ |
| | | DN baseDn = DN.decode(dn); |
| | | PersistentServerState state = new PersistentServerState(baseDn, (short) 1); |
| | | ChangeNumberGenerator gen1 = new ChangeNumberGenerator((short) 1, state); |
| | | ChangeNumberGenerator gen2 = new ChangeNumberGenerator((short) 2, state); |
| | | ServerState origState = new ServerState(); |
| | | PersistentServerState state = |
| | | new PersistentServerState(baseDn, (short) 1, origState); |
| | | ChangeNumberGenerator gen1 = |
| | | new ChangeNumberGenerator((short) 1, origState); |
| | | ChangeNumberGenerator gen2 = |
| | | new ChangeNumberGenerator((short) 2, origState); |
| | | |
| | | ChangeNumber cn1 = gen1.newChangeNumber(); |
| | | ChangeNumber cn2 = gen2.newChangeNumber(); |
| | |
| | | private static final short RS2_ID = 32; |
| | | private int rs1Port = -1; |
| | | private int rs2Port = -1; |
| | | private ReplicationDomain rd1 = null; |
| | | private ReplicationDomain rd2 = null; |
| | | private LDAPReplicationDomain rd1 = null; |
| | | private LDAPReplicationDomain rd2 = null; |
| | | private ReplicationServer rs1 = null; |
| | | private ReplicationServer rs2 = null; |
| | | |
| | |
| | | { |
| | | |
| | | int rsPort = -1; |
| | | ReplicationDomain rd = null; |
| | | LDAPReplicationDomain rd = null; |
| | | switch (dsId) |
| | | { |
| | | case DS1_ID: |
| | |
| | | /** |
| | | * Creates a new ReplicationDomain. |
| | | */ |
| | | private ReplicationDomain createReplicationDomain(DN baseDn, short serverId) |
| | | private LDAPReplicationDomain createReplicationDomain(DN baseDn, short serverId) |
| | | { |
| | | |
| | | SortedSet<String> replServers = new TreeSet<String>(); |
| | |
| | | DomainFakeCfg domainConf = |
| | | new DomainFakeCfg(baseDn, serverId, replServers); |
| | | //domainConf.setHeartbeatInterval(500); |
| | | ReplicationDomain replicationDomain = |
| | | LDAPReplicationDomain replicationDomain = |
| | | MultimasterReplication.createNewDomain(domainConf); |
| | | replicationDomain.start(); |
| | | |
| | |
| | | return null; |
| | | } |
| | | |
| | | private int findReplServerConnected(ReplicationDomain rd) |
| | | private int findReplServerConnected(LDAPReplicationDomain rd) |
| | | { |
| | | int rsPort = -1; |
| | | |
| | |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | import static org.opends.server.replication.plugin.ReplicationBroker.*; |
| | | |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | import static org.opends.server.loggers.debug.DebugLogger.getTracer; |
| | |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.common.DSInfo; |
| | | import org.opends.server.replication.common.ServerState; |
| | |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | 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.server.ReplServerFakeConfiguration; |
| | | import org.opends.server.replication.server.ReplicationServer; |
| | | import org.opends.server.types.Attribute; |
| | |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | import static org.testng.Assert.*; |
| | | import static org.opends.server.TestCaseUtils.*; |
| | | |
| | | /** |
| | | * Some tests to go through the DS state machine and validate we get the |
| | |
| | | private static final short DS3_ID = 3; |
| | | private static final short RS1_ID = 41; |
| | | private int rs1Port = -1; |
| | | private ReplicationDomain ds1 = null; |
| | | private LDAPReplicationDomain ds1 = null; |
| | | private ReplicationBroker ds2 = null; |
| | | private ReplicationBroker ds3 = null; |
| | | private ReplicationServer rs1 = null; |
| | |
| | | { |
| | | |
| | | ReplicationBroker rb = null; |
| | | ReplicationDomain rd = null; |
| | | LDAPReplicationDomain rd = null; |
| | | switch (dsId) |
| | | { |
| | | case DS1_ID: |
| | |
| | | // Sleep 1 second |
| | | try |
| | | { |
| | | Thread.sleep(1000); |
| | | Thread.sleep(100); |
| | | } catch (InterruptedException ex) |
| | | { |
| | | fail("Error sleeping " + stackTraceToSingleLineString(ex)); |
| | | } |
| | | nSec++; |
| | | |
| | | if (nSec > secTimeout) |
| | | if (nSec > secTimeout*10) |
| | | { |
| | | // Timeout reached, end with error |
| | | fail("checkConnection: DS " + dsId + " is not connected to the RS after " |
| | |
| | | * Creates and starts a new ReplicationDomain configured for the replication |
| | | * server |
| | | */ |
| | | private ReplicationDomain createReplicationDomain(short dsId) |
| | | private LDAPReplicationDomain createReplicationDomain(short dsId) |
| | | { |
| | | try |
| | | { |
| | |
| | | DN baseDn = DN.decode(EXAMPLE_DN); |
| | | DomainFakeCfg domainConf = |
| | | new DomainFakeCfg(baseDn, dsId, replServers); |
| | | ReplicationDomain replicationDomain = |
| | | LDAPReplicationDomain replicationDomain = |
| | | MultimasterReplication.createNewDomain(domainConf); |
| | | replicationDomain.start(); |
| | | |
| | |
| | | throws Exception, SocketException |
| | | { |
| | | ReplicationBroker broker = new ReplicationBroker(null, |
| | | state, DN.decode(EXAMPLE_DN), dsId, 0, 0, 0, 0, window, 0, generationId, |
| | | state, EXAMPLE_DN, dsId, 100, generationId, 0, |
| | | new ReplSessionSecurity(null, null, null, true), (byte) 1); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | | servers.add("localhost:" + rs1Port); |
| | |
| | | |
| | | try |
| | | { |
| | | |
| | | /** |
| | | * RS1 starts with specified threshold value |
| | | */ |
| | |
| | | // having sent them to TCP receive queue of DS2. |
| | | bw = new BrokerWriter(ds3, DS3_ID, false); |
| | | bw.followAndPause(11); |
| | | sleep(1000); |
| | | // sleep(1000); |
| | | |
| | | /** |
| | | * DS3 sends changes (less than threshold): DS2 should still be in normal |
| | |
| | | { |
| | | nChangesSent = thresholdValue - 1; |
| | | bw.followAndPause(nChangesSent); |
| | | sleep(7000); // Be sure status analyzer has time to test |
| | | sleep(1000); // Be sure status analyzer has time to test |
| | | ReplicationMsg msg = br3.getLastMsg(); |
| | | debugInfo(testCase + " Step 1: last message from writer: " + msg); |
| | | assertTrue(msg == null); |
| | | assertTrue(msg == null, (msg != null) ? msg.toString() : "null" ); |
| | | } |
| | | |
| | | /** |
| | |
| | | * update topo message with status of DS2: degraded status |
| | | */ |
| | | bw.followAndPause(thresholdValue - nChangesSent); |
| | | sleep(7000); // Be sure status analyzer has time to test |
| | | ReplicationMsg lastMsg = br3.getLastMsg(); |
| | | assertTrue(lastMsg != null); |
| | | debugInfo(testCase + " Step 2: last message from writer: " + lastMsg); |
| | | assertTrue(lastMsg instanceof TopologyMsg); |
| | | TopologyMsg topoMsg = (TopologyMsg) lastMsg; |
| | | List<DSInfo> dsList = topoMsg.getDsList(); |
| | | assertEquals(dsList.size(), 1); |
| | | DSInfo ds3Info = dsList.get(0); |
| | | assertEquals(ds3Info.getDsId(), DS2_ID); |
| | | assertEquals(ds3Info.getStatus(), ServerStatus.DEGRADED_STATUS); |
| | | // wait for a status MSG status analyzer to broker 3 |
| | | ReplicationMsg lastMsg = null; |
| | | for (int count = 0; count< 50; count++) |
| | | { |
| | | List<DSInfo> dsList = ds3.getDsList(); |
| | | DSInfo ds3Info = null; |
| | | if (dsList.size() > 0) |
| | | { |
| | | ds3Info = dsList.get(0); |
| | | } |
| | | if ((ds3Info != null) && (ds3Info.getDsId() == DS2_ID) && |
| | | (ds3Info.getStatus()== ServerStatus.DEGRADED_STATUS) ) |
| | | { |
| | | break; |
| | | } |
| | | else |
| | | { |
| | | if (count < 50) |
| | | sleep(200); // Be sure status analyzer has time to test |
| | | else |
| | | fail("DS2 did not get degraded : " + ds3Info); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * DS3 sends 10 additional changes after threshold value, DS2 should still be |
| | |
| | | */ |
| | | bw.followAndPause(10); |
| | | bw.shutdown(); |
| | | sleep(7000); // Be sure status analyzer has time to test |
| | | sleep(1000); // Be sure status analyzer has time to test |
| | | lastMsg = br3.getLastMsg(); |
| | | ReplicationMsg msg = br3.getLastMsg(); |
| | | debugInfo(testCase + " Step 3: last message from writer: " + msg); |
| | |
| | | * (create a reader to emulate replay of messages (messages read from queue)) |
| | | */ |
| | | br2 = new BrokerReader(ds2, DS2_ID); |
| | | sleep(7000); // Be sure messages are read and status analyzer has time to test |
| | | lastMsg = br3.getLastMsg(); |
| | | assertTrue(lastMsg != null); |
| | | debugInfo(testCase + " Step 4: last message from writer: " + lastMsg); |
| | | assertTrue(lastMsg instanceof TopologyMsg); |
| | | topoMsg = (TopologyMsg) lastMsg; |
| | | dsList = topoMsg.getDsList(); |
| | | assertEquals(dsList.size(), 1); |
| | | ds3Info = dsList.get(0); |
| | | assertEquals(ds3Info.getDsId(), DS2_ID); |
| | | assertEquals(ds3Info.getStatus(), ServerStatus.NORMAL_STATUS); |
| | | // wait for a status MSG status analyzer to broker 3 |
| | | for (int count = 0; count< 50; count++) |
| | | { |
| | | List<DSInfo> dsList = ds3.getDsList(); |
| | | DSInfo ds3Info = null; |
| | | if (dsList.size() > 0) |
| | | { |
| | | ds3Info = dsList.get(0); |
| | | } |
| | | if ((ds3Info != null) && (ds3Info.getDsId() == DS2_ID) && |
| | | (ds3Info.getStatus()== ServerStatus.DEGRADED_STATUS) ) |
| | | { |
| | | break; |
| | | } |
| | | else |
| | | { |
| | | if (count < 50) |
| | | sleep(200); // Be sure status analyzer has time to test |
| | | else |
| | | fail("DS2 did not get degraded."); |
| | | } |
| | | } |
| | | |
| | | } finally |
| | | { |
| | |
| | | rs1.remove(); |
| | | bw.pause(); |
| | | sleepAssertStatusEquals(30, ds1, ServerStatus.NOT_CONNECTED_STATUS); |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * DS2 restarts with up to date server state (this allows to have |
| | | * restarting RS1 not sending him some updates he already sent) |
| | |
| | | * DS2 sends reset gen id order with bad gen id: DS1 should go in bad gen id |
| | | * status (from degraded status this time) |
| | | */ |
| | | resetGenId(ds2, -1); // -1 to allow next step full update and flush RS db so that DS1 can reconnect after full update |
| | | resetGenId(ds2, -1); // -1 to allow next step full update and flush RS db so that DS1 can reconnect after full update |
| | | sleepAssertStatusEquals(30, ds1, ServerStatus.BAD_GEN_ID_STATUS); |
| | | bw.pause(); |
| | | |
| | |
| | | ds2.stop(); // will need a new broker with another gen id restart it |
| | | bw.shutdown(); |
| | | br.shutdown(); |
| | | long newGen = ds1.getGenerationId(); |
| | | long newGen = ds1.getGenerationID(); |
| | | curState = ds1.getServerState(); |
| | | ds2 = createReplicationBroker(DS2_ID, curState, newGen); |
| | | checkConnection(30, DS2_ID); |
| | |
| | | ds2.stop(); // will need a new broker with another gen id restart it |
| | | bw.shutdown(); |
| | | br.shutdown(); |
| | | newGen = ds1.getGenerationId(); |
| | | newGen = ds1.getGenerationID(); |
| | | curState = ds1.getServerState(); |
| | | ds2 = createReplicationBroker(DS2_ID, curState, newGen); |
| | | checkConnection(30, DS2_ID); |
| | |
| | | // a backend backed up with a file |
| | | |
| | | // Clear the backend |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | } |
| | | |
| | |
| | | super.classCleanUp(); |
| | | |
| | | // Clear the backend |
| | | ReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | LDAPReplicationDomain.clearJEBackend(false, "userRoot", EXAMPLE_DN); |
| | | |
| | | paranoiaCheck(); |
| | | } |
| | | |
| | |
| | | private short destId = -1; // Server id of server to initialize |
| | | private long nEntries = -1; // Number of entries to send to dest |
| | | private boolean createReader = false; |
| | | |
| | | |
| | | /** |
| | | * If the BrokerInitializer is to be used for a lot of entries to send |
| | | * (which is often the case), the reader thread should be enabled to make |
| | |
| | | * method of the broker himself. |
| | | */ |
| | | private BrokerReader reader = null; |
| | | |
| | | |
| | | /** |
| | | * Creates a broker initializer with a reader |
| | | */ |
| | |
| | | this.serverId = serverId; |
| | | this.createReader = createReader; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Initializes a full update session by sending InitializeTargetMsg |
| | | */ |
| | |
| | | |
| | | // Send init msg to warn dest server it is going do be initialized |
| | | RoutableMsg initTargetMsg = null; |
| | | try |
| | | { |
| | | initTargetMsg = |
| | | new InitializeTargetMsg(DN.decode(EXAMPLE_DN), serverId, destId, |
| | | |
| | | initTargetMsg = |
| | | new InitializeTargetMsg(EXAMPLE_DN, serverId, destId, |
| | | serverId, nEntries); |
| | | } catch (DirectoryException ex) |
| | | { |
| | | fail("Unable to create init message."); |
| | | } |
| | | |
| | | rb.publish(initTargetMsg); |
| | | |
| | | // Send top entry for the domain |
| | |
| | | public void runFullUpdate() |
| | | { |
| | | debugInfo("Broker " + serverId + " initializer starting sending entries to server " + destId); |
| | | |
| | | |
| | | for(long i = 0 ; i<nEntries ; i++) { |
| | | EntryMsg entryMsg = createNextEntryMsg(); |
| | | rb.publish(entryMsg); |
| | | } |
| | | |
| | | debugInfo("Broker " + serverId + " initializer stopping sending entries"); |
| | | |
| | | |
| | | debugInfo("Broker " + serverId + " initializer sending EntryDoneMsg"); |
| | | DoneMsg doneMsg = new DoneMsg(serverId, destId); |
| | | rb.publish(doneMsg); |
| | | |
| | | |
| | | if (createReader) |
| | | { |
| | | reader.shutdown(); |
| | | } |
| | | |
| | | |
| | | debugInfo("Broker " + serverId + " initializer thread is dying"); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Thread for sending a lot of changes through a broker. |
| | | */ |
| | |
| | | private int nChangesSent = 0; // Number of sent changes |
| | | private int nChangesSentLimit = 0; |
| | | ChangeNumberGenerator gen = null; |
| | | private Object sleeper = new Object(); |
| | | /** |
| | | * If the BrokerWriter is to be used for a lot of changes to send (which is |
| | | * often the case), the reader thread should be enabled to make the window |
| | |
| | | try |
| | | { |
| | | // Writer in pause, sleep a while to let other threads work |
| | | Thread.sleep(1000); |
| | | synchronized(sleeper) |
| | | { |
| | | sleeper.wait(1000); |
| | | } |
| | | } catch (InterruptedException ex) |
| | | { |
| | | /* Don't care */ |
| | |
| | | { |
| | | suspended.set(true); // If were working |
| | | shutdown.set(true); |
| | | synchronized (sleeper) |
| | | { |
| | | sleeper.notify(); |
| | | } |
| | | try |
| | | { |
| | | join(); |
| | |
| | | { |
| | | /* Don't care */ |
| | | } |
| | | |
| | | |
| | | // Stop reader if any |
| | | if (reader != null) |
| | | { |
| | |
| | | { |
| | | try |
| | | { |
| | | Thread.sleep(1000); |
| | | Thread.sleep(200); |
| | | } catch (InterruptedException ex) |
| | | { |
| | | /* Don't care */ |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public void followAndPause(int nChanges) |
| | | { |
| | | debugInfo("Requested broker writer " + serverId + " to write " + nChanges + " change(s)."); |
| | | debugInfo("Requested broker writer " + serverId + " to write " + nChanges + " change(s)."); |
| | | pause(); // If however we were already working |
| | | |
| | | // Initialize counter system variables |
| | |
| | | * @param testedValue The value we want to test |
| | | * @param expectedValue The value the tested value should be equal to |
| | | */ |
| | | private void sleepAssertStatusEquals(int secTimeout, ReplicationDomain testedValue, |
| | | private void sleepAssertStatusEquals(int secTimeout, LDAPReplicationDomain testedValue, |
| | | ServerStatus expectedValue) |
| | | { |
| | | int nSec = 0; |
| | |
| | | import java.util.List; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | import static org.opends.server.replication.plugin.ReplicationBroker.*; |
| | | |
| | | import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; |
| | | import static org.opends.server.loggers.ErrorLogger.logError; |
| | | import static org.opends.server.loggers.debug.DebugLogger.getTracer; |
| | |
| | | static |
| | | { |
| | | DS2_RU.add("ldap://fake_url_for_ds2"); |
| | | |
| | | |
| | | DS6_RU.add("ldap://fake_url_for_ds6_A"); |
| | | DS6_RU.add("ldap://fake_url_for_ds6_B"); |
| | | } |
| | | |
| | | |
| | | private int rs1Port = -1; |
| | | private int rs2Port = -1; |
| | | private int rs3Port = -1; |
| | | private ReplicationDomain rd1 = null; |
| | | private ReplicationDomain rd2 = null; |
| | | private ReplicationDomain rd3 = null; |
| | | private ReplicationDomain rd4 = null; |
| | | private ReplicationDomain rd5 = null; |
| | | private ReplicationDomain rd6 = null; |
| | | private LDAPReplicationDomain rd1 = null; |
| | | private LDAPReplicationDomain rd2 = null; |
| | | private LDAPReplicationDomain rd3 = null; |
| | | private LDAPReplicationDomain rd4 = null; |
| | | private LDAPReplicationDomain rd5 = null; |
| | | private LDAPReplicationDomain rd6 = null; |
| | | private ReplicationServer rs1 = null; |
| | | private ReplicationServer rs2 = null; |
| | | private ReplicationServer rs3 = null; |
| | |
| | | rd6.shutdown(); |
| | | rd6 = null; |
| | | } |
| | | |
| | | |
| | | try |
| | | { |
| | | // Clear any reference to a domain in synchro plugin |
| | |
| | | private void checkConnection(int secTimeout, short dsId, short rsId) |
| | | { |
| | | int rsPort = -1; |
| | | ReplicationDomain rd = null; |
| | | LDAPReplicationDomain rd = null; |
| | | switch (dsId) |
| | | { |
| | | case DS1_ID: |
| | |
| | | } |
| | | |
| | | return replServers; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Creates a new ReplicationServer. |
| | |
| | | * Creates and starts a new ReplicationDomain with the correct list of |
| | | * know RSs according to DS id |
| | | */ |
| | | private ReplicationDomain createReplicationDomain(short dsId) |
| | | private LDAPReplicationDomain createReplicationDomain(short dsId) |
| | | { |
| | | try |
| | | { |
| | |
| | | DomainFakeCfg domainConf = |
| | | new DomainFakeCfg(baseDn, dsId, replServers, assuredType, |
| | | assuredSdLevel, groupId, 0, refUrls); |
| | | ReplicationDomain replicationDomain = |
| | | LDAPReplicationDomain replicationDomain = |
| | | MultimasterReplication.createNewDomain(domainConf); |
| | | replicationDomain.start(); |
| | | |
| | |
| | | |
| | | return new TopoView(dsList, rsList); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Get the topo view each DS in the provided ds list has and compares it |
| | | * with the theoretical topology view that every body should have at the time |
| | |
| | | { |
| | | for(short currentDsId : dsIdList) |
| | | { |
| | | ReplicationDomain rd = null; |
| | | |
| | | LDAPReplicationDomain rd = null; |
| | | |
| | | switch (currentDsId) |
| | | { |
| | | case DS1_ID: |
| | |
| | | default: |
| | | fail("Unknown replication domain server id."); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Get the topo view of the current analyzed DS |
| | | */ |
| | |
| | | dsList.add(aDsInfo); |
| | | } |
| | | short dsId = rd.getServerId(); |
| | | short rsId = rd.getBroker().getRsServerId(); |
| | | short rsId = rd.getRsServerId(); |
| | | ServerStatus status = rd.getStatus(); |
| | | boolean assuredFlag = rd.isAssured(); |
| | | AssuredMode assuredMode = rd.getAssuredMode(); |
| | |
| | | DSInfo dsInfo = new DSInfo(dsId, rsId, TEST_DN_WITH_ROOT_ENTRY_GENID, status, assuredFlag, assuredMode, |
| | | safeDataLevel, groupId, refUrls); |
| | | dsList.add(dsInfo); |
| | | |
| | | |
| | | TopoView dsTopoView = new TopoView(dsList, rd.getRsList()); |
| | | |
| | | |
| | | /** |
| | | * Compare to what is the expected view |
| | | */ |
| | | |
| | | |
| | | assertEquals(dsTopoView, theoricalTopoView); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Bag class representing a view of the topology at a given time |
| | | * (who is connected to who, what are the config parameters...) |
| | |
| | | private class TopoView |
| | | { |
| | | private List<DSInfo> dsList = null; |
| | | private List<RSInfo> rsList = null; |
| | | |
| | | private List<RSInfo> rsList = null; |
| | | |
| | | public TopoView(List<DSInfo> dsList, List<RSInfo> rsList) |
| | | { |
| | | assertNotNull(dsList); |
| | | assertNotNull(rsList); |
| | | |
| | | |
| | | this.dsList = dsList; |
| | | this.rsList = rsList; |
| | | } |
| | | |
| | | |
| | | public boolean equals(Object obj) |
| | | { |
| | | assertNotNull(obj); |
| | | assertFalse(obj.getClass() != this.getClass()); |
| | | |
| | | |
| | | TopoView topoView = (TopoView) obj; |
| | | |
| | | // Check dsList |
| | |
| | | @DataProvider(name="createReplServerStartData") |
| | | public Object [][] createReplServerStartData() throws Exception |
| | | { |
| | | DN baseDN = DN.decode("o=test"); |
| | | String baseDN = "o=test"; |
| | | ServerState state = new ServerState(); |
| | | state.update(new ChangeNumber((long)0, 0,(short)0)); |
| | | Object[] set1 = new Object[] {(short)1, baseDN, 0, "localhost:8989", state, 0L, (byte)0, 0}; |
| | | |
| | | baseDN = DN.decode("dc=example,dc=com"); |
| | | baseDN = "dc=example,dc=com"; |
| | | state = new ServerState(); |
| | | state.update(new ChangeNumber((long)75, 5,(short)263)); |
| | | Object[] set2 = new Object[] {(short)16, baseDN, 100, "anotherHost:1025", state, 1245L, (byte)25, 3456}; |
| | |
| | | * using protocol V1 and V2 are working. |
| | | */ |
| | | @Test(dataProvider="createReplServerStartData") |
| | | public void replServerStartMsgTest(short serverId, DN baseDN, int window, |
| | | public void replServerStartMsgTest(short serverId, String baseDN, int window, |
| | | String url, ServerState state, long genId, byte groupId, int degTh) throws Exception |
| | | { |
| | | // Create V2 message |
| | |
| | | assertEquals(msg.getGroupId(), v2Msg.getGroupId()); |
| | | assertEquals(msg.getDegradedStatusThreshold(), v2Msg.getDegradedStatusThreshold()); |
| | | } |
| | | |
| | | |
| | | @DataProvider(name = "createAddData") |
| | | public Object[][] createAddData() { |
| | | return new Object[][] { |
| | |
| | | |
| | | // Check default value for only V2 fields |
| | | assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)-1); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)1); |
| | | |
| | | // Set again only V2 fields |
| | | newMsg.setAssuredMode(assuredMode); |
| | |
| | | |
| | | // Check default value for only V2 fields |
| | | assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)-1); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)1); |
| | | |
| | | // Set again only V2 fields |
| | | newMsg.setAssuredMode(assuredMode); |
| | |
| | | assertEquals(msg.getAssuredMode(), v2Msg.getAssuredMode()); |
| | | assertEquals(msg.getSafeDataLevel(), v2Msg.getSafeDataLevel()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Build some data for the ModifyMsg test below. |
| | | */ |
| | |
| | | |
| | | // Check default value for only V2 fields |
| | | assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)-1); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)1); |
| | | |
| | | // Set again only V2 fields |
| | | newMsg.setAssuredMode(assuredMode); |
| | |
| | | genModOpBasis.getAttachment(SYNCHROCONTEXT)); |
| | | assertEquals(modOpBasis.getModifications(), genModOpBasis.getModifications()); |
| | | } |
| | | |
| | | |
| | | @DataProvider(name = "createModifyDnData") |
| | | public Object[][] createModifyDnData() { |
| | | |
| | |
| | | |
| | | // Check default value for only V2 fields |
| | | assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)-1); |
| | | assertEquals(newMsg.getSafeDataLevel(), (byte)1); |
| | | assertEquals(modDnOpBasis.getModifications(), mods); |
| | | assertTrue(genModDnOpBasis.getModifications() == null); |
| | | |
| | |
| | | */ |
| | | package org.opends.server.replication.protocol; |
| | | |
| | | 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.assertTrue; |
| | | import static org.testng.Assert.fail; |
| | | import static org.opends.server.replication.protocol.OperationContext.*; |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.util.Iterator; |
| | | import java.util.ArrayList; |
| | |
| | | assertEquals(msg.getAssuredMode(), assuredMode); |
| | | |
| | | // Check safe data level |
| | | assertTrue(msg.getSafeDataLevel() == -1); |
| | | assertTrue(msg.getSafeDataLevel() == 1); |
| | | msg.setSafeDataLevel(safeDataLevel); |
| | | assertTrue(msg.getSafeDataLevel() == safeDataLevel); |
| | | |
| | |
| | | assertEquals(op.getRawEntryDN(), mod2.getRawEntryDN()); |
| | | |
| | | // Create an update message from this op |
| | | DeleteMsg updateMsg = (DeleteMsg) UpdateMsg.generateMsg(op); |
| | | DeleteMsg updateMsg = (DeleteMsg) LDAPUpdateMsg.generateMsg(op); |
| | | assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber()); |
| | | } |
| | | |
| | |
| | | assertEquals(moddn1.getModifications(), moddn2.getModifications()); |
| | | |
| | | // Create an update message from this op |
| | | ModifyDNMsg updateMsg = (ModifyDNMsg) UpdateMsg.generateMsg(localOp); |
| | | ModifyDNMsg updateMsg = (ModifyDNMsg) LDAPUpdateMsg.generateMsg(localOp); |
| | | assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber()); |
| | | } |
| | | |
| | |
| | | |
| | | |
| | | // Create an update message from this op |
| | | AddMsg updateMsg = (AddMsg) UpdateMsg.generateMsg(addOp); |
| | | AddMsg updateMsg = (AddMsg) LDAPUpdateMsg.generateMsg(addOp); |
| | | assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber()); |
| | | } |
| | | |
| | |
| | | @DataProvider(name="createServerStartData") |
| | | public Object [][] createServerStartData() throws Exception |
| | | { |
| | | DN baseDN = DN.decode(TEST_ROOT_DN_STRING); |
| | | String baseDN = TEST_ROOT_DN_STRING; |
| | | ServerState state = new ServerState(); |
| | | state.update(new ChangeNumber((long)0, 0,(short)0)); |
| | | Object[] set1 = new Object[] {(short)1, baseDN, 0, state, 0L, false, (byte)0}; |
| | | |
| | | baseDN = DN.decode(TEST_ROOT_DN_STRING); |
| | | state = new ServerState(); |
| | | state.update(new ChangeNumber((long)75, 5,(short)263)); |
| | | Object[] set2 = new Object[] {(short)16, baseDN, 100, state, 1248L, true, (byte)31}; |
| | |
| | | * by checking that : msg == new ServerStartMsg(msg.getBytes()). |
| | | */ |
| | | @Test(dataProvider="createServerStartData") |
| | | public void serverStartMsgTest(short serverId, DN baseDN, int window, |
| | | public void serverStartMsgTest(short serverId, String baseDN, int window, |
| | | ServerState state, long genId, boolean sslEncryption, byte groupId) throws Exception |
| | | { |
| | | ServerStartMsg msg = new ServerStartMsg(serverId, baseDN, |
| | |
| | | @DataProvider(name="createReplServerStartData") |
| | | public Object [][] createReplServerStartData() throws Exception |
| | | { |
| | | DN baseDN = DN.decode(TEST_ROOT_DN_STRING); |
| | | String baseDN = TEST_ROOT_DN_STRING; |
| | | ServerState state = new ServerState(); |
| | | state.update(new ChangeNumber((long)0, 0,(short)0)); |
| | | Object[] set1 = new Object[] {(short)1, baseDN, 0, "localhost:8989", state, 0L, (byte)0, 0}; |
| | | |
| | | baseDN = DN.decode(TEST_ROOT_DN_STRING); |
| | | state = new ServerState(); |
| | | state.update(new ChangeNumber((long)75, 5,(short)263)); |
| | | Object[] set2 = new Object[] {(short)16, baseDN, 100, "anotherHost:1025", state, 1245L, (byte)25, 3456}; |
| | |
| | | * by checking that : msg == new ReplServerStartMsg(msg.getBytes()). |
| | | */ |
| | | @Test(dataProvider="createReplServerStartData") |
| | | public void replServerStartMsgTest(short serverId, DN baseDN, int window, |
| | | public void replServerStartMsgTest(short serverId, String baseDN, int window, |
| | | String url, ServerState state, long genId, byte groupId, int degTh) throws Exception |
| | | { |
| | | ReplServerStartMsg msg = new ReplServerStartMsg(serverId, |
| | |
| | | |
| | | List<String> urls3 = new ArrayList<String>(); |
| | | urls3.add("ldaps://host:port/dc=foo??sub?(sn=One Entry)"); |
| | | |
| | | |
| | | List<String> urls4 = new ArrayList<String>(); |
| | | urls4.add("ldaps://host:port/dc=foobar1??sub?(sn=Another Entry 1)"); |
| | | urls4.add("ldaps://host:port/dc=foobar2??sub?(sn=Another Entry 2)"); |
| | |
| | | |
| | | DSInfo dsInfo3 = new DSInfo((short)2436, (short)591, (long)0, ServerStatus.NORMAL_STATUS, |
| | | false, AssuredMode.SAFE_READ_MODE, (byte)17, (byte)0, urls3); |
| | | |
| | | |
| | | DSInfo dsInfo4 = new DSInfo((short)415, (short)146, (long)0, ServerStatus.BAD_GEN_ID_STATUS, |
| | | true, AssuredMode.SAFE_DATA_MODE, (byte)2, (byte)15, urls4); |
| | | |
| | |
| | | assertTrue(msg.getSafeDataLevel() == newMsg.getSafeDataLevel()); |
| | | assertEquals(msg.getReferralsURLs(), newMsg.getReferralsURLs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Provider for the ChangeStatusMsg test |
| | | */ |
| | |
| | | { |
| | | HeartbeatMsg msg = new HeartbeatMsg(); |
| | | HeartbeatMsg newMsg = new HeartbeatMsg(msg.getBytes()); |
| | | assertNotNull(newMsg); |
| | | } |
| | | |
| | | /** |
| | |
| | | ResetGenerationIdMsg newMsg = new ResetGenerationIdMsg(msg.getBytes()); |
| | | assertEquals(msg.getGenerationId(), newMsg.getGenerationId()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Test MonitorRequestMsg encoding and decoding. |
| | | */ |
| | |
| | | { |
| | | MonitorRequestMsg msg = new MonitorRequestMsg((short)1,(short)2); |
| | | MonitorRequestMsg newMsg = new MonitorRequestMsg(msg.getBytes()); |
| | | assertEquals(newMsg.getDestination(), 2); |
| | | assertEquals(newMsg.getsenderID(), 1); |
| | | } |
| | | |
| | | /** |
| | |
| | | msg.setServerState(sid1, s1, now+1, true); |
| | | msg.setServerState(sid2, s2, now+2, true); |
| | | msg.setServerState(sid3, s3, now+3, false); |
| | | |
| | | |
| | | byte[] b = msg.getBytes(); |
| | | MonitorMsg newMsg = new MonitorMsg(b); |
| | | |
| | | assertEquals(rsState, msg.getReplServerDbState()); |
| | | assertEquals(newMsg.getReplServerDbState().toString(), |
| | | assertEquals(newMsg.getReplServerDbState().toString(), |
| | | msg.getReplServerDbState().toString()); |
| | | |
| | | |
| | | Iterator<Short> it = newMsg.ldapIterator(); |
| | | while (it.hasNext()) |
| | | { |
| | |
| | | } |
| | | else if (sid == sid2) |
| | | { |
| | | assertEquals(s.toString(), s2.toString()); |
| | | assertEquals(s.toString(), s2.toString()); |
| | | assertEquals((Long)(now+2), newMsg.getLDAPApproxFirstMissingDate(sid), ""); |
| | | } |
| | | else |
| | |
| | | "objectclass: top\n" + |
| | | "objectclass: ds-task\n" + |
| | | "objectclass: ds-task-initialize\n" + |
| | | "ds-task-class-name: org.opends.server.tasks.InitializeTask\n" + |
| | | "ds-task-class-name: org.opends.server.replication.api.InitializeTask\n" + |
| | | "ds-task-initialize-domain-dn: " + TEST_ROOT_DN_STRING + "\n" + |
| | | "ds-task-initialize-source: 1\n"); |
| | | short sender = 1; |
| | |
| | | short sender = 1; |
| | | short target = 2; |
| | | InitializeRequestMsg msg = new InitializeRequestMsg( |
| | | DN.decode(TEST_ROOT_DN_STRING), sender, target); |
| | | TEST_ROOT_DN_STRING, sender, target); |
| | | InitializeRequestMsg newMsg = new InitializeRequestMsg(msg.getBytes()); |
| | | assertEquals(msg.getsenderID(), newMsg.getsenderID()); |
| | | assertEquals(msg.getDestination(), newMsg.getDestination()); |
| | |
| | | short targetID = 2; |
| | | short requestorID = 3; |
| | | long entryCount = 4; |
| | | DN baseDN = DN.decode(TEST_ROOT_DN_STRING); |
| | | |
| | | InitializeTargetMsg msg = new InitializeTargetMsg( |
| | | baseDN, senderID, targetID, requestorID, entryCount); |
| | | TEST_ROOT_DN_STRING, senderID, targetID, requestorID, entryCount); |
| | | InitializeTargetMsg newMsg = new InitializeTargetMsg(msg.getBytes()); |
| | | assertEquals(msg.getsenderID(), newMsg.getsenderID()); |
| | | assertEquals(msg.getDestination(), newMsg.getDestination()); |
| | |
| | | assertEquals(targetID, newMsg.getDestination()); |
| | | assertEquals(requestorID, newMsg.getRequestorID()); |
| | | assertEquals(entryCount, newMsg.getEntryCount()); |
| | | assertTrue(baseDN.equals(newMsg.getBaseDN())) ; |
| | | assertTrue(TEST_ROOT_DN_STRING.equals(newMsg.getBaseDN())) ; |
| | | |
| | | } |
| | | |
| | |
| | | assertEquals(msg.getMsgID(), newMsg.getMsgID()); |
| | | assertEquals(msg.getDetails(), newMsg.getDetails()); |
| | | } |
| | | |
| | | /** |
| | | * Test Generic UpdateMsg |
| | | */ |
| | | @Test |
| | | public void UpdateMsgTest() throws Exception |
| | | { |
| | | final String test = "string used for test"; |
| | | UpdateMsg msg = |
| | | new UpdateMsg( |
| | | new ChangeNumber((long) 1, 2 , (short)3), |
| | | test.getBytes()); |
| | | UpdateMsg newMsg = new UpdateMsg(msg.getBytes()); |
| | | assertEquals(test.getBytes(), newMsg.getPayload()); |
| | | } |
| | | } |
| | |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.protocol.DeleteMsg; |
| | | import org.opends.server.types.DN; |
| | | import org.testng.annotations.Test; |
| | | import static org.testng.Assert.*; |
| | | import static org.opends.server.TestCaseUtils.*; |
| | |
| | | |
| | | dbEnv = new ReplicationDbEnv(path, replicationServer); |
| | | |
| | | handler = new DbHandler((short) 1, DN.decode(TEST_ROOT_DN_STRING), |
| | | handler = new DbHandler((short) 1, TEST_ROOT_DN_STRING, |
| | | replicationServer, dbEnv, 5000); |
| | | |
| | | ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 1, 0); |
| | |
| | | dbEnv = new ReplicationDbEnv(path, replicationServer); |
| | | |
| | | handler = |
| | | new DbHandler((short) 1, DN.decode(TEST_ROOT_DN_STRING), |
| | | new DbHandler((short) 1, TEST_ROOT_DN_STRING, |
| | | replicationServer, dbEnv, 5000); |
| | | |
| | | // Creates changes added to the dbHandler |
| | |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationDomain; |
| | | import org.opends.server.replication.plugin.LDAPReplicationDomain; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.replication.protocol.SocketSession; |
| | |
| | | private ReplicationServer replServer2 = null; |
| | | private ReplicationServer replServer3 = null; |
| | | private boolean emptyOldChanges = true; |
| | | ReplicationDomain replDomain = null; |
| | | LDAPReplicationDomain replDomain = null; |
| | | SocketSession ssSession = null; |
| | | boolean ssShutdownRequested = false; |
| | | protected String[] updatedEntries; |
| | |
| | | "Unable to add the synchronized server"); |
| | | configEntryList.add(synchroServerEntry.getDN()); |
| | | |
| | | replDomain = ReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | replDomain = LDAPReplicationDomain.retrievesReplicationDomain(baseDn); |
| | | |
| | | if (replDomain != null) |
| | | { |
| | |
| | | |
| | | debugInfo("Connecting DS to replServer1"); |
| | | connectServer1ToChangelog(changelog1ID); |
| | | Thread.sleep(1500); |
| | | |
| | | try |
| | | { |
| | |
| | | |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.types.DN; |
| | | import org.testng.annotations.Test; |
| | | import static org.opends.server.TestCaseUtils.*; |
| | | |
| | | /** |
| | | * Tests that we can dynamically modify the configuration of replicationServer |
| | | * Tests that we can dynamically modify the configuration of replicationServer |
| | | */ |
| | | |
| | | public class ReplicationServerDynamicConfTest extends ReplicationTestCase |
| | |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.ldap.LDAPFilter; |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.service.ReplicationBroker; |
| | | import org.opends.server.replication.common.ChangeNumber; |
| | | import org.opends.server.replication.common.ChangeNumberGenerator; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.common.ServerStatus; |
| | | import org.opends.server.replication.plugin.MultimasterReplication; |
| | | import org.opends.server.replication.plugin.ReplicationBroker; |
| | | import org.opends.server.replication.plugin.ReplicationServerListener; |
| | | import org.opends.server.replication.protocol.AddMsg; |
| | | import org.opends.server.replication.protocol.DeleteMsg; |
| | |
| | | { |
| | | replicationServer.clearDb(); |
| | | changelogBasic(); |
| | | multipleWriterMultipleReader(); |
| | | newClientLateServer1(); |
| | | newClient(); |
| | | newClientWithFirstChanges(); |
| | | newClientWithChangefromServer1(); |
| | | newClientWithChangefromServer2(); |
| | | newClientWithUnknownChanges(); |
| | | oneWriterMultipleReader(); |
| | | changelogChaining(); |
| | | stopChangelog(); |
| | | exportBackend(); |
| | |
| | | windowProbeTest(); |
| | | replicationServerConnected(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * This test allows to check the behavior of the Replication Server |
| | | * when the DS disconnect and reconnect again. |
| | |
| | | server2 = openReplicationSession( |
| | | DN.decode(TEST_ROOT_DN_STRING), (short) 2, 100, replicationServerPort, |
| | | 1000, true); |
| | | |
| | | |
| | | assertTrue(server1.isConnected()); |
| | | assertTrue(server2.isConnected()); |
| | | |
| | |
| | | "other-uid"); |
| | | server2.publish(msg); |
| | | msg2 = server1.receive(); |
| | | if (!(msg2 instanceof TopologyMsg)) |
| | | fail("ReplicationServer basic : incorrect message type received: " + |
| | | msg2.getClass().toString() + ": content: " + msg2.toString()); |
| | | msg2 = server1.receive(); |
| | | server1.updateWindowAfterReplay(); |
| | | if (msg2 instanceof DeleteMsg) |
| | | { |
| | |
| | | else |
| | | fail("ReplicationServer basic : incorrect message type received: " + |
| | | msg2.getClass().toString() + ": content: " + msg2.toString()); |
| | | |
| | | |
| | | debugInfo("Ending changelogBasic"); |
| | | } |
| | | finally |
| | |
| | | * This test is configured by a relatively low stress |
| | | * but can be changed using TOTAL_MSG and CLIENT_THREADS consts. |
| | | */ |
| | | private void oneWriterMultipleReader() throws Exception |
| | | @Test(enabled=true) |
| | | public void oneWriterMultipleReader() throws Exception |
| | | { |
| | | debugInfo("Starting oneWriterMultipleReader"); |
| | | |
| | | replicationServer.clearDb(); |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | ReplicationBroker server = null; |
| | | BrokerReader reader = null; |
| | | int TOTAL_MSG = 1000; // number of messages to send during the test |
| | |
| | | if (clientBroker[i] != null) |
| | | clientBroker[i].stop(); |
| | | } |
| | | |
| | | replicationServer.clearDb(); |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | } |
| | | } |
| | | |
| | |
| | | * This test is configured for a relatively low stress |
| | | * but can be changed using TOTAL_MSG and THREADS consts. |
| | | */ |
| | | private void multipleWriterMultipleReader() throws Exception |
| | | @Test() |
| | | public void multipleWriterMultipleReader() throws Exception |
| | | { |
| | | debugInfo("Starting multipleWriterMultipleReader"); |
| | | final int TOTAL_MSG = 1000; // number of messages to send during the test |
| | |
| | | BrokerReader reader[] = new BrokerReader[THREADS]; |
| | | ReplicationBroker broker[] = new ReplicationBroker[THREADS]; |
| | | |
| | | replicationServer.clearDb(); |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | try |
| | | { |
| | | /* |
| | |
| | | if (broker[i] != null) |
| | | broker[i].stop(); |
| | | } |
| | | |
| | | replicationServer.clearDb(); |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | } |
| | | } |
| | | |
| | |
| | | // client2 will be created later |
| | | broker1 = openReplicationSession(DN.decode(TEST_ROOT_DN_STRING), |
| | | brokerIds[0], 100, changelogPorts[0], 1000, !emptyOldChanges); |
| | | |
| | | |
| | | assertTrue(broker1.isConnected()); |
| | | |
| | | if (itest == 0) |
| | |
| | | { |
| | | // send a ServerStartMsg with an empty ServerState. |
| | | ServerStartMsg msg = |
| | | new ServerStartMsg((short) 1723, DN.decode(TEST_ROOT_DN_STRING), |
| | | new ServerStartMsg((short) 1723, TEST_ROOT_DN_STRING, |
| | | 0, 0, 0, 0, WINDOW, (long) 5000, new ServerState(), |
| | | ProtocolVersion.getCurrentVersion(), 0, sslEncryption, (byte)-1); |
| | | session.publish(msg); |
| | |
| | | // received. |
| | | DN baseDn = DN.decode(TEST_ROOT_DN_STRING); |
| | | msg = new ServerStartMsg( |
| | | (short) 1724, baseDn, |
| | | (short) 1724, TEST_ROOT_DN_STRING, |
| | | 0, 0, 0, 0, WINDOW, (long) 5000, replServerState, |
| | | ProtocolVersion.getCurrentVersion(), |
| | | ReplicationTestCase.getGenerationId(baseDn), |
| | |
| | | * @throws Exception If the environment could not be set up. |
| | | */ |
| | | @AfterClass |
| | | |
| | | |
| | | public void classCleanUp() throws Exception |
| | | { |
| | | callParanoiaCheck = false; |
| | |
| | | * Testing searches on the backend of the replication server. |
| | | * @throws Exception |
| | | */ |
| | | // TODO: this test disabled as testReplicationBackendACIs() is failing |
| | | // : anonymous search returns entries from replication backend whereas it |
| | | // should not. Probably a previous test in the nightlytests suite is |
| | | // removing/modifying some ACIs...When problem foound, we have to re-enable |
| | | // this test. |
| | | @Test(enabled=false) |
| | | @Test(enabled=true) |
| | | public void searchBackend() throws Exception |
| | | { |
| | | debugInfo("Starting searchBackend"); |
| | |
| | | LDAPFilter.decode("(changetype=*)")); |
| | | assertEquals(op.getResultCode(), ResultCode.NO_SUCH_OBJECT); |
| | | |
| | | testReplicationBackendACIs(); |
| | | // TODO: testReplicationBackendACIs() is disabled because it |
| | | // is currently failing when run in the nightly target. |
| | | // anonymous search returns entries from replication backend whereas it |
| | | // should not. Probably a previous test in the nightlytests suite is |
| | | // removing/modifying some ACIs...When problem foound, we have to re-enable |
| | | // this test. |
| | | // testReplicationBackendACIs(); |
| | | |
| | | // General search |
| | | op = connection.processSearch( |
| | |
| | | assertEquals(op.getSearchEntries().size(), 1); |
| | | |
| | | /* |
| | | * It would be nice to be have the abilities to search for |
| | | * entries in the replication backend using the DN on which the |
| | | * It would be nice to be have the abilities to search for |
| | | * entries in the replication backend using the DN on which the |
| | | * operation was done as the search criteria. |
| | | * This is not possible yet, this part of the test is therefore |
| | | * This is not possible yet, this part of the test is therefore |
| | | * disabled. |
| | | * |
| | | * debugInfo("Query / searchBase"); |
| | |
| | | attrs2); |
| | | assertEquals(op.getResultCode(), ResultCode.SUCCESS); |
| | | assertEquals(op.getSearchEntries().size(), 5); |
| | | |
| | | |
| | | debugInfo("Successfully ending searchBackend"); |
| | | |
| | | } finally { |
| | |
| | | |
| | | broker2 = openReplicationSession(DN.decode(TEST_ROOT_DN_STRING), |
| | | brokerIds[1], 100, changelogPorts[1], 1000, emptyOldChanges); |
| | | |
| | | |
| | | assertTrue(broker1.isConnected()); |
| | | assertTrue(broker2.isConnected()); |
| | | |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | | import org.opends.server.types.ResultCode; |
| | | import java.io.IOException; |
| | | import java.util.concurrent.BlockingQueue; |
| | | import org.opends.server.config.ConfigException; |
| | | import java.util.Collection; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.types.DirectoryException; |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | |
| | | /** |
| | | * This class is the minimum implementation of a Concrete ReplicationDomain |
| | | * used to test the Generic Replication Service. |
| | | */ |
| | | public class FakeReplicationDomain extends ReplicationDomain |
| | | { |
| | | // A blocking queue that is used to send the UpdateMsg received from |
| | | // the Replication Service. |
| | | BlockingQueue<UpdateMsg> queue = null; |
| | | |
| | | // A string that will be exported should exportBackend be called. |
| | | String exportString = null; |
| | | |
| | | // A StringBuilder that will be used to build a build a new String should the |
| | | // import be called. |
| | | StringBuilder importString = null; |
| | | |
| | | private int exportedEntryCount; |
| | | |
| | | public FakeReplicationDomain( |
| | | String serviceID, |
| | | short serverID, |
| | | Collection<String> replicationServers, |
| | | int window, |
| | | long heartbeatInterval, |
| | | BlockingQueue<UpdateMsg> queue) throws ConfigException |
| | | { |
| | | super(serviceID, serverID); |
| | | startPublishService(replicationServers, window, heartbeatInterval); |
| | | startListenService(); |
| | | this.queue = queue; |
| | | } |
| | | |
| | | public FakeReplicationDomain( |
| | | String serviceID, |
| | | short serverID, |
| | | Collection<String> replicationServers, |
| | | int window, |
| | | long heartbeatInterval, |
| | | String exportString, |
| | | StringBuilder importString, |
| | | int exportedEntryCount) throws ConfigException |
| | | { |
| | | super(serviceID, serverID); |
| | | startPublishService(replicationServers, window, heartbeatInterval); |
| | | startListenService(); |
| | | this.exportString = exportString; |
| | | this.importString = importString; |
| | | this.exportedEntryCount = exportedEntryCount; |
| | | } |
| | | |
| | | @Override |
| | | public long countEntries() throws DirectoryException |
| | | { |
| | | return exportedEntryCount; |
| | | } |
| | | |
| | | @Override |
| | | protected void exportBackend(OutputStream output) throws DirectoryException |
| | | { |
| | | try |
| | | { |
| | | output.write(exportString.getBytes()); |
| | | output.flush(); |
| | | output.close(); |
| | | } |
| | | catch (IOException e) |
| | | { |
| | | throw new DirectoryException(ResultCode.OPERATIONS_ERROR, |
| | | ERR_BACKEND_EXPORT_ENTRY.get("", "")); |
| | | } |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public long getGenerationID() |
| | | { |
| | | return 1; |
| | | } |
| | | |
| | | @Override |
| | | protected void importBackend(InputStream input) throws DirectoryException |
| | | { |
| | | byte[] buffer = new byte[1000]; |
| | | |
| | | int ret; |
| | | do |
| | | { |
| | | try |
| | | { |
| | | ret = input.read(buffer, 0, 1000); |
| | | } catch (IOException e) |
| | | { |
| | | throw new DirectoryException( |
| | | ResultCode.OPERATIONS_ERROR, |
| | | ERR_BACKEND_EXPORT_ENTRY.get("", "")); |
| | | } |
| | | importString.append(new String(buffer, 0, ret)); |
| | | } |
| | | while (ret >= 0); |
| | | } |
| | | |
| | | @Override |
| | | public boolean processUpdate(UpdateMsg updateMsg) |
| | | { |
| | | if (queue != null) |
| | | queue.add(updateMsg); |
| | | return true; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | | import org.opends.server.types.ResultCode; |
| | | import java.io.IOException; |
| | | import java.util.concurrent.BlockingQueue; |
| | | import org.opends.server.config.ConfigException; |
| | | import java.util.Collection; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.types.DirectoryException; |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | |
| | | /** |
| | | * This class is the minimum implementation of a Concrete ReplicationDomain |
| | | * used to test the Generic Replication Service. |
| | | */ |
| | | public class FakeStressReplicationDomain extends ReplicationDomain |
| | | { |
| | | // A blocking queue that is used to send the UpdateMsg received from |
| | | // the Replication Service. |
| | | BlockingQueue<UpdateMsg> queue = null; |
| | | |
| | | // A string that will be exported should exportBackend be called. |
| | | String exportString = null; |
| | | |
| | | // A StringBuilder that will be used to build a build a new String should the |
| | | // import be called. |
| | | StringBuilder importString = null; |
| | | |
| | | public FakeStressReplicationDomain( |
| | | String serviceID, |
| | | short serverID, |
| | | Collection<String> replicationServers, |
| | | int window, |
| | | long heartbeatInterval, |
| | | BlockingQueue<UpdateMsg> queue) throws ConfigException |
| | | { |
| | | super(serviceID, serverID); |
| | | startPublishService(replicationServers, window, heartbeatInterval); |
| | | startListenService(); |
| | | this.queue = queue; |
| | | } |
| | | |
| | | public FakeStressReplicationDomain( |
| | | String serviceID, |
| | | short serverID, |
| | | Collection<String> replicationServers, |
| | | int window, |
| | | long heartbeatInterval, |
| | | String exportString, |
| | | StringBuilder importString) throws ConfigException |
| | | { |
| | | super(serviceID, serverID); |
| | | startPublishService(replicationServers, window, heartbeatInterval); |
| | | startListenService(); |
| | | this.exportString = exportString; |
| | | this.importString = importString; |
| | | } |
| | | |
| | | final int IMPORT_SIZE = 100000000; |
| | | @Override |
| | | public long countEntries() throws DirectoryException |
| | | { |
| | | return IMPORT_SIZE; |
| | | } |
| | | |
| | | @Override |
| | | protected void exportBackend(OutputStream output) throws DirectoryException |
| | | { |
| | | System.out.println("export started"); |
| | | try |
| | | { |
| | | for (int i=0; i< IMPORT_SIZE; i++) |
| | | { |
| | | output.write("this is a long key like a dn or something similar: value\n\n".getBytes()); |
| | | } |
| | | output.flush(); |
| | | output.close(); |
| | | } |
| | | catch (IOException e) |
| | | { |
| | | throw new DirectoryException(ResultCode.OPERATIONS_ERROR, |
| | | ERR_BACKEND_EXPORT_ENTRY.get("", "")); |
| | | } |
| | | System.out.println("export finished"); |
| | | } |
| | | |
| | | @Override |
| | | public long getGenerationID() |
| | | { |
| | | return 1; |
| | | } |
| | | |
| | | @Override |
| | | protected void importBackend(InputStream input) throws DirectoryException |
| | | { |
| | | long startDate = System.currentTimeMillis(); |
| | | System.out.println("import started : " + startDate); |
| | | |
| | | byte[] buffer = new byte[10000]; |
| | | int ret; |
| | | int count = 0; |
| | | do |
| | | { |
| | | try |
| | | { |
| | | ret = input.read(buffer, 0, 10000); |
| | | } catch (IOException e) |
| | | { |
| | | e.printStackTrace(); |
| | | throw new DirectoryException( |
| | | ResultCode.OPERATIONS_ERROR, |
| | | ERR_BACKEND_EXPORT_ENTRY.get("", "")); |
| | | } |
| | | count++; |
| | | } |
| | | while (ret >= 0); |
| | | |
| | | long endDate = System.currentTimeMillis(); |
| | | System.out.println("import end : " + endDate); |
| | | System.out.println("import duration (sec): " + (endDate - startDate) / 1000); |
| | | System.out.println("import count: " + count); |
| | | } |
| | | |
| | | @Override |
| | | public boolean processUpdate(UpdateMsg updateMsg) |
| | | { |
| | | if (queue != null) |
| | | queue.add(updateMsg); |
| | | return true; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.util.TreeSet; |
| | | |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | |
| | | import java.net.ServerSocket; |
| | | import java.util.ArrayList; |
| | | import java.util.concurrent.BlockingQueue; |
| | | import java.util.concurrent.TimeUnit; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.replication.ReplicationTestCase; |
| | | import org.opends.server.replication.common.DSInfo; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.replication.server.ReplServerFakeConfiguration; |
| | | import org.opends.server.replication.server.ReplicationServer; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Test the Generic Replication Service. |
| | | */ |
| | | public class ReplicationDomainTest extends ReplicationTestCase |
| | | { |
| | | /** |
| | | * Test that a ReplicationDomain is able to publish and receive UpdateMsg. |
| | | */ |
| | | @Test(enabled=true) |
| | | public void publishAndReceive() throws Exception |
| | | { |
| | | String testService = "test"; |
| | | ReplicationServer replServer = null; |
| | | int replServerID = 10; |
| | | FakeReplicationDomain domain1 = null; |
| | | FakeReplicationDomain domain2 = null; |
| | | |
| | | try |
| | | { |
| | | // find a free port for the replicationServer |
| | | ServerSocket socket = TestCaseUtils.bindFreePort(); |
| | | int replServerPort = socket.getLocalPort(); |
| | | socket.close(); |
| | | |
| | | ReplServerFakeConfiguration conf = |
| | | new ReplServerFakeConfiguration( |
| | | replServerPort, "ReplicationDomainTestDb", |
| | | 0, replServerID, 0, 100, null); |
| | | |
| | | replServer = new ReplicationServer(conf); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | | servers.add("localhost:" + replServerPort); |
| | | |
| | | BlockingQueue<UpdateMsg> rcvQueue1 = new LinkedBlockingQueue<UpdateMsg>(); |
| | | domain1 = new FakeReplicationDomain( |
| | | testService, (short) 1, servers, 100, 1000, rcvQueue1); |
| | | |
| | | BlockingQueue<UpdateMsg> rcvQueue2 = new LinkedBlockingQueue<UpdateMsg>(); |
| | | domain2 = new FakeReplicationDomain( |
| | | testService, (short) 2, servers, 100, 1000, rcvQueue2); |
| | | |
| | | /* |
| | | * Publish a message from domain1, |
| | | * Check that domain2 receives it shortly after. |
| | | */ |
| | | byte[] test = {1, 2, 3 ,4, 0, 1, 2, 3, 4, 5}; |
| | | domain1.publish(test); |
| | | |
| | | UpdateMsg rcvdMsg = rcvQueue2.poll(1, TimeUnit.SECONDS); |
| | | assertNotNull(rcvdMsg); |
| | | assertEquals(test, rcvdMsg.getPayload()); |
| | | } |
| | | finally |
| | | { |
| | | if (domain1 != null) |
| | | domain1.disableService(); |
| | | |
| | | if (domain2 != null) |
| | | domain2.disableService(); |
| | | |
| | | if (replServer != null) |
| | | replServer.remove(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Test that a ReplicationDomain is able to export and import its database. |
| | | */ |
| | | @Test(enabled=true) |
| | | public void exportAndImport() throws Exception |
| | | { |
| | | final int ENTRYCOUNT=5000; |
| | | String testService = "test"; |
| | | ReplicationServer replServer = null; |
| | | int replServerID = 11; |
| | | FakeReplicationDomain domain1 = null; |
| | | FakeReplicationDomain domain2 = null; |
| | | |
| | | try |
| | | { |
| | | // find a free port for the replicationServer |
| | | ServerSocket socket = TestCaseUtils.bindFreePort(); |
| | | int replServerPort = socket.getLocalPort(); |
| | | socket.close(); |
| | | |
| | | ReplServerFakeConfiguration conf = |
| | | new ReplServerFakeConfiguration( |
| | | replServerPort, "ReplicationDomainTestDb", |
| | | 0, replServerID, 0, 100, null); |
| | | |
| | | replServer = new ReplicationServer(conf); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | | servers.add("localhost:" + replServerPort); |
| | | |
| | | StringBuilder exportedDataBuilder = new StringBuilder(); |
| | | for (int i =0; i<ENTRYCOUNT; i++) |
| | | { |
| | | exportedDataBuilder.append("key : value"+i+"\n\n"); |
| | | } |
| | | String exportedData=exportedDataBuilder.toString(); |
| | | domain1 = new FakeReplicationDomain( |
| | | testService, (short) 1, servers, |
| | | 100, 0, exportedData, null, ENTRYCOUNT); |
| | | |
| | | StringBuilder importedData = new StringBuilder(); |
| | | domain2 = new FakeReplicationDomain( |
| | | testService, (short) 2, servers, 100, 0, |
| | | null, importedData, 0); |
| | | |
| | | /* |
| | | * Trigger a total update from domain1 to domain2. |
| | | * Check that the exported data is correctly received on domain2. |
| | | */ |
| | | for (DSInfo remoteDS : domain2.getDsList()) |
| | | { |
| | | if (remoteDS.getDsId() != domain2.getServerId()) |
| | | { |
| | | domain2.initializeFromRemote(remoteDS.getDsId() , null); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | int count = 0; |
| | | while ((importedData.length() < exportedData.length()) && (count < 500)) |
| | | { |
| | | count ++; |
| | | Thread.sleep(100); |
| | | } |
| | | assertTrue(domain2.getLeftEntryCount() == 0, |
| | | "LeftEntryCount for export is " + domain2.getLeftEntryCount()); |
| | | assertTrue(domain1.getLeftEntryCount() == 0, |
| | | "LeftEntryCount for import is " + domain1.getLeftEntryCount()); |
| | | assertEquals(importedData.length(), exportedData.length()); |
| | | assertEquals(importedData.toString(), exportedData); |
| | | } |
| | | finally |
| | | { |
| | | if (domain1 != null) |
| | | domain1.disableService(); |
| | | |
| | | if (domain2 != null) |
| | | domain2.disableService(); |
| | | |
| | | if (replServer != null) |
| | | replServer.remove(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Sender side of the Total Update Perf test. |
| | | * The goal of this test is to measure the performance |
| | | * of the total update code. |
| | | * It is not intended to be run as part of the daily unit test but |
| | | * should only be used manually by developer in need of testing the |
| | | * performance improvement or non-regression of the total update code. |
| | | * Use this test in combination with the receiverInitialize() : |
| | | * - enable the test |
| | | * - start the senderInitialize first using |
| | | * ./build.sh \ |
| | | * -Dorg.opends.test.suppressOutput=false \ |
| | | * -Dtest.methods=org.opends.server.replication.service.ReplicationDomainTest.senderInitialize test |
| | | * - start the receiverInitialize second. |
| | | * - you may want to change HOST1 and HOST2 to use 2 different hosts |
| | | * if you don't want to do a loopback test. |
| | | * - don't forget to disable again the tests after running them |
| | | */ |
| | | final String HOST1 = "localhost:"; |
| | | final String HOST2 = "localhost:"; |
| | | final int SENDERPORT = 10102; |
| | | final int RECEIVERPORT = 10101; |
| | | |
| | | @Test(enabled=false) |
| | | public void senderInitialize() throws Exception |
| | | { |
| | | String testService = "test"; |
| | | ReplicationServer replServer = null; |
| | | int replServerID = 12; |
| | | FakeStressReplicationDomain domain1 = null; |
| | | |
| | | try |
| | | { |
| | | TreeSet<String> servers = new TreeSet<String>(); |
| | | servers.add(HOST1 + SENDERPORT); |
| | | servers.add(HOST2 + RECEIVERPORT); |
| | | |
| | | ReplServerFakeConfiguration conf = |
| | | new ReplServerFakeConfiguration( |
| | | SENDERPORT, "ReplicationDomainTestDb", |
| | | 0, replServerID, 0, 100, servers); |
| | | |
| | | replServer = new ReplicationServer(conf); |
| | | |
| | | BlockingQueue<UpdateMsg> rcvQueue1 = new LinkedBlockingQueue<UpdateMsg>(); |
| | | domain1 = new FakeStressReplicationDomain( |
| | | testService, (short) 2, servers, 100, 1000, rcvQueue1); |
| | | |
| | | System.out.println("waiting"); |
| | | Thread.sleep(1000000000); |
| | | } |
| | | finally |
| | | { |
| | | if (domain1 != null) |
| | | domain1.disableService(); |
| | | |
| | | if (replServer != null) |
| | | replServer.remove(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * See comments in senderInitialize() above |
| | | */ |
| | | @Test(enabled=false) |
| | | public void receiverInitialize() throws Exception |
| | | { |
| | | String testService = "test"; |
| | | ReplicationServer replServer = null; |
| | | int replServerID = 11; |
| | | FakeStressReplicationDomain domain1 = null; |
| | | |
| | | try |
| | | { |
| | | TreeSet<String> servers = new TreeSet<String>(); |
| | | servers.add(HOST1 + SENDERPORT); |
| | | servers.add(HOST2 + RECEIVERPORT); |
| | | |
| | | ReplServerFakeConfiguration conf = |
| | | new ReplServerFakeConfiguration( |
| | | RECEIVERPORT, "ReplicationDomainTestDb", |
| | | 0, replServerID, 0, 100, servers); |
| | | |
| | | replServer = new ReplicationServer(conf); |
| | | |
| | | BlockingQueue<UpdateMsg> rcvQueue1 = new LinkedBlockingQueue<UpdateMsg>(); |
| | | domain1 = new FakeStressReplicationDomain( |
| | | testService, (short) 1, servers, 100, 100000, rcvQueue1); |
| | | /* |
| | | * Trigger a total update from domain1 to domain2. |
| | | * Check that the exported data is correctly received on domain2. |
| | | */ |
| | | boolean alone = true; |
| | | while (alone) |
| | | { |
| | | for (DSInfo remoteDS : domain1.getDsList()) |
| | | { |
| | | if (remoteDS.getDsId() != domain1.getServerId()) |
| | | { |
| | | alone = false; |
| | | domain1.initializeFromRemote(remoteDS.getDsId() , null); |
| | | break; |
| | | } |
| | | } |
| | | if (alone) |
| | | { |
| | | System.out.println("trying..."); |
| | | Thread.sleep(1000); |
| | | } |
| | | } |
| | | System.out.println("waiting"); |
| | | Thread.sleep(10000000); |
| | | } |
| | | finally |
| | | { |
| | | if (domain1 != null) |
| | | domain1.disableService(); |
| | | |
| | | if (replServer != null) |
| | | replServer.remove(); |
| | | } |
| | | } |
| | | } |