001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.replication.server;
018
019import static org.opends.messages.ConfigMessages.*;
020import static org.opends.messages.ReplicationMessages.*;
021import static org.opends.server.util.StaticUtils.*;
022
023import java.io.IOException;
024import java.net.*;
025import java.util.*;
026import java.util.concurrent.CopyOnWriteArraySet;
027import java.util.concurrent.atomic.AtomicBoolean;
028
029import org.forgerock.i18n.LocalizableMessage;
030import org.forgerock.i18n.slf4j.LocalizedLogger;
031import org.forgerock.opendj.config.server.ConfigChangeResult;
032import org.forgerock.opendj.config.server.ConfigException;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.SearchScope;
035import org.opends.server.admin.server.ConfigurationChangeListener;
036import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn.ConflictBehavior;
037import org.opends.server.admin.std.server.ReplicationServerCfg;
038import org.opends.server.admin.std.server.UserDefinedVirtualAttributeCfg;
039import org.opends.server.api.VirtualAttributeProvider;
040import org.opends.server.backends.ChangelogBackend;
041import org.opends.server.core.DirectoryServer;
042import org.opends.server.replication.common.CSN;
043import org.opends.server.replication.common.MultiDomainServerState;
044import org.opends.server.replication.common.ServerState;
045import org.opends.server.replication.plugin.MultimasterReplication;
046import org.opends.server.replication.protocol.ReplServerStartMsg;
047import org.opends.server.replication.protocol.ReplSessionSecurity;
048import org.opends.server.replication.protocol.ReplicationMsg;
049import org.opends.server.replication.protocol.ServerStartMsg;
050import org.opends.server.replication.protocol.Session;
051import org.opends.server.replication.server.changelog.api.ChangeNumberIndexDB;
052import org.opends.server.replication.server.changelog.api.ChangeNumberIndexRecord;
053import org.opends.server.replication.server.changelog.api.ChangelogDB;
054import org.opends.server.replication.server.changelog.api.ChangelogException;
055import org.opends.server.replication.server.changelog.file.ECLEnabledDomainPredicate;
056import org.opends.server.replication.server.changelog.file.FileChangelogDB;
057import org.opends.server.replication.service.DSRSShutdownSync;
058import org.forgerock.opendj.ldap.schema.AttributeType;
059import org.forgerock.opendj.ldap.DN;
060import org.opends.server.types.DirectoryException;
061import org.opends.server.types.HostPort;
062import org.opends.server.types.SearchFilter;
063import org.opends.server.types.VirtualAttributeRule;
064
065/**
066 * ReplicationServer Listener. This singleton is the main object of the
067 * replication server. It waits for the incoming connections and create listener
068 * and publisher objects for connection with LDAP servers and with replication
069 * servers It is responsible for creating the replication server
070 * replicationServerDomain and managing it
071 */
072public class ReplicationServer
073  implements ConfigurationChangeListener<ReplicationServerCfg>
074{
075  private String serverURL;
076
077  private ServerSocket listenSocket;
078  private Thread listenThread;
079  private Thread connectThread;
080
081  /** The current configuration of this replication server. */
082  private ReplicationServerCfg config;
083  private final DSRSShutdownSync dsrsShutdownSync;
084
085  /**
086   * This table is used to store the list of dn for which we are currently
087   * handling servers.
088   */
089  private final Map<DN, ReplicationServerDomain> baseDNs = new HashMap<>();
090
091  /** The database storing the changes. */
092  private final ChangelogDB changelogDB;
093
094  /** The backend that allow to search the changes (external changelog). */
095  private ChangelogBackend changelogBackend;
096
097  private final AtomicBoolean shutdown = new AtomicBoolean();
098  private boolean stopListen;
099  private final ReplSessionSecurity replSessionSecurity;
100
101  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
102
103  /** To know whether a domain is enabled for the external changelog. */
104  private final ECLEnabledDomainPredicate domainPredicate;
105
106  /**
107   * This is required for unit testing, so that we can keep track of all the
108   * replication servers which are running in the VM.
109   */
110  private static final Set<Integer> localPorts = new CopyOnWriteArraySet<>();
111
112  /** Monitors for synchronizing domain creation with the connect thread. */
113  private final Object domainTicketLock = new Object();
114  private final Object connectThreadLock = new Object();
115  private long domainTicket;
116
117  /**
118   * Holds the list of all replication servers instantiated in this VM.
119   * This allows to perform clean up of the RS databases in unit tests.
120   */
121  private static final List<ReplicationServer> allInstances = new ArrayList<>();
122
123  /**
124   * Creates a new Replication server using the provided configuration entry.
125   *
126   * @param cfg The configuration of this replication server.
127   * @throws ConfigException When Configuration is invalid.
128   */
129  public ReplicationServer(ReplicationServerCfg cfg) throws ConfigException
130  {
131    this(cfg, new DSRSShutdownSync(), new ECLEnabledDomainPredicate());
132  }
133
134  /**
135   * Creates a new Replication server using the provided configuration entry and shutdown
136   * synchronization object.
137   *
138   * @param cfg The configuration of this replication server.
139   * @param dsrsShutdownSync Synchronization object for shutdown of combined DS/RS instances.
140   * @throws ConfigException When Configuration is invalid.
141   */
142  public ReplicationServer(ReplicationServerCfg cfg, DSRSShutdownSync dsrsShutdownSync) throws ConfigException
143  {
144    this(cfg, dsrsShutdownSync, new ECLEnabledDomainPredicate());
145  }
146
147  /**
148   * Creates a new Replication server using the provided configuration entry, shutdown
149   * synchronization object and domain predicate.
150   *
151   * @param cfg The configuration of this replication server.
152   * @param dsrsShutdownSync Synchronization object for shutdown of combined DS/RS instances.
153   * @param predicate Indicates whether a domain is enabled for the external changelog.
154   * @throws ConfigException When Configuration is invalid.
155   */
156  public ReplicationServer(final ReplicationServerCfg cfg, final DSRSShutdownSync dsrsShutdownSync,
157      final ECLEnabledDomainPredicate predicate) throws ConfigException
158  {
159    this.config = cfg;
160    this.dsrsShutdownSync = dsrsShutdownSync;
161    this.domainPredicate = predicate;
162
163    enableExternalChangeLog();
164    this.changelogDB = new FileChangelogDB(this, config.getReplicationDBDirectory());
165
166    replSessionSecurity = new ReplSessionSecurity();
167    initialize();
168    cfg.addChangeListener(this);
169
170    localPorts.add(getReplicationPort());
171
172    // Keep track of this new instance
173    allInstances.add(this);
174  }
175
176  private Set<HostPort> getConfiguredRSAddresses()
177  {
178    final Set<HostPort> results = new HashSet<>();
179    for (String serverAddress : this.config.getReplicationServer())
180    {
181      results.add(HostPort.valueOf(serverAddress));
182    }
183    return results;
184  }
185
186  /**
187   * Get the list of every replication servers instantiated in the current VM.
188   * @return The list of every replication servers instantiated in the current
189   * VM.
190   */
191  public static List<ReplicationServer> getAllInstances()
192  {
193    return allInstances;
194  }
195
196  /**
197   * The run method for the Listen thread.
198   * This thread accept incoming connections on the replication server
199   * ports from other replication servers or from LDAP servers
200   * and spawn further thread responsible for handling those connections
201   */
202  void runListen()
203  {
204    logger.info(NOTE_REPLICATION_SERVER_LISTENING,
205        getServerId(),
206        listenSocket.getInetAddress().getHostAddress(),
207        listenSocket.getLocalPort());
208
209    while (!shutdown.get() && !stopListen)
210    {
211      // Wait on the replicationServer port.
212      // Read incoming messages and create LDAP or ReplicationServer listener
213      // and Publisher.
214      try
215      {
216        Session session;
217        Socket newSocket = null;
218        try
219        {
220          newSocket = listenSocket.accept();
221          newSocket.setTcpNoDelay(true);
222          newSocket.setKeepAlive(true);
223          int timeoutMS = MultimasterReplication.getConnectionTimeoutMS();
224          session = replSessionSecurity.createServerSession(newSocket,
225              timeoutMS);
226          if (session == null) // Error, go back to accept
227          {
228            continue;
229          }
230        }
231        catch (Exception e)
232        {
233          // If problems happen during the SSL handshake, it is necessary
234          // to close the socket to free the associated resources.
235          if (newSocket != null)
236          {
237            newSocket.close();
238          }
239          continue;
240        }
241
242        ReplicationMsg msg = session.receive();
243
244        final int queueSize = this.config.getQueueSize();
245        final int rcvWindow = this.config.getWindowSize();
246        if (msg instanceof ServerStartMsg)
247        {
248          DataServerHandler dsHandler = new DataServerHandler(
249              session, queueSize, this, rcvWindow);
250          dsHandler.startFromRemoteDS((ServerStartMsg) msg);
251        }
252        else if (msg instanceof ReplServerStartMsg)
253        {
254          ReplicationServerHandler rsHandler = new ReplicationServerHandler(
255              session, queueSize, this, rcvWindow);
256          rsHandler.startFromRemoteRS((ReplServerStartMsg) msg);
257        }
258        else
259        {
260          // We did not recognize the message, close session as what
261          // can happen after is undetermined and we do not want the server to
262          // be disturbed
263          session.close();
264          return;
265        }
266      }
267      catch (Exception e)
268      {
269        // The socket has probably been closed as part of the
270        // shutdown or changing the port number process.
271        // Just log debug information and loop.
272        // Do not log the message during shutdown.
273        logger.traceException(e);
274        if (!shutdown.get())
275        {
276          logger.error(ERR_EXCEPTION_LISTENING, e.getLocalizedMessage());
277        }
278      }
279    }
280  }
281
282  /**
283   * This method manages the connection with the other replication servers.
284   * It periodically checks that this replication server is indeed connected
285   * to all the other replication servers and if not attempts to
286   * make the connection.
287   */
288  void runConnect()
289  {
290    synchronized (connectThreadLock)
291    {
292      while (!shutdown.get())
293      {
294        HostPort localAddress = HostPort.localAddress(getReplicationPort());
295        for (ReplicationServerDomain domain : getReplicationServerDomains())
296        {
297          /*
298           * If there are N RSs configured then we will usually be connected to
299           * N-1 of them, since one of them is usually this RS. However, we
300           * cannot guarantee this since the configuration may not contain this
301           * RS.
302           */
303          final Set<HostPort> connectedRSAddresses =
304              getConnectedRSAddresses(domain);
305          for (HostPort rsAddress : getConfiguredRSAddresses())
306          {
307            if (connectedRSAddresses.contains(rsAddress))
308            {
309              continue; // Skip: already connected.
310            }
311
312            // FIXME: this will need changing if we ever support listening on
313            // specific addresses.
314            if (rsAddress.equals(localAddress))
315            {
316              continue; // Skip: avoid connecting to self.
317            }
318
319            connect(rsAddress, domain.getBaseDN());
320          }
321        }
322
323        // Notify any threads waiting with domain tickets after each iteration.
324        synchronized (domainTicketLock)
325        {
326          domainTicket++;
327          domainTicketLock.notifyAll();
328        }
329
330        // Retry each second.
331        final int randomizer = (int) (Math.random() * 100);
332        try
333        {
334          // Releases lock, allows threads to get domain ticket.
335          connectThreadLock.wait(1000 + randomizer);
336        }
337        catch (InterruptedException e)
338        {
339          // Signaled to shutdown.
340          return;
341        }
342      }
343    }
344  }
345
346  private Set<HostPort> getConnectedRSAddresses(ReplicationServerDomain domain)
347  {
348    Set<HostPort> results = new HashSet<>();
349    for (ReplicationServerHandler rsHandler : domain.getConnectedRSs().values())
350    {
351      results.add(HostPort.valueOf(rsHandler.getServerAddressURL()));
352    }
353    return results;
354  }
355
356  /**
357   * Establish a connection to the server with the address and port.
358   *
359   * @param remoteServerAddress
360   *          The address and port for the server
361   * @param baseDN
362   *          The baseDN of the connection
363   */
364  private void connect(HostPort remoteServerAddress, DN baseDN)
365  {
366    boolean sslEncryption = replSessionSecurity.isSslEncryption();
367
368    if (logger.isTraceEnabled())
369    {
370      logger.trace("RS " + getMonitorInstanceName() + " connects to "
371          + remoteServerAddress);
372    }
373
374    Socket socket = new Socket();
375    Session session = null;
376    try
377    {
378      socket.setTcpNoDelay(true);
379      if (config.getSourceAddress() != null)
380      {
381        InetSocketAddress local = new InetSocketAddress(config.getSourceAddress(), 0);
382        socket.bind(local);
383      }
384      int timeoutMS = MultimasterReplication.getConnectionTimeoutMS();
385      socket.connect(remoteServerAddress.toInetSocketAddress(), timeoutMS);
386      session = replSessionSecurity.createClientSession(socket, timeoutMS);
387
388      ReplicationServerHandler rsHandler = new ReplicationServerHandler(
389          session, config.getQueueSize(), this, config.getWindowSize());
390      rsHandler.connect(baseDN, sslEncryption);
391    }
392    catch (Exception e)
393    {
394      logger.traceException(e);
395      close(session);
396      close(socket);
397    }
398  }
399
400  /** Initialization function for the replicationServer. */
401  private void initialize()
402  {
403    shutdown.set(false);
404
405    try
406    {
407      this.changelogDB.initializeDB();
408
409      setServerURL();
410      listenSocket = new ServerSocket();
411      listenSocket.bind(new InetSocketAddress(getReplicationPort()));
412
413      // creates working threads: we must first connect, then start to listen.
414      if (logger.isTraceEnabled())
415      {
416        logger.trace("RS " + getMonitorInstanceName() + " creates connect thread");
417      }
418      connectThread = new ReplicationServerConnectThread(this);
419      connectThread.start();
420
421      if (logger.isTraceEnabled())
422      {
423        logger.trace("RS " + getMonitorInstanceName() + " creates listen thread");
424      }
425
426      listenThread = new ReplicationServerListenThread(this);
427      listenThread.start();
428
429      if (logger.isTraceEnabled())
430      {
431        logger.trace("RS " + getMonitorInstanceName() + " successfully initialized");
432      }
433    } catch (UnknownHostException e)
434    {
435      logger.error(ERR_UNKNOWN_HOSTNAME);
436    } catch (IOException e)
437    {
438      logger.error(ERR_COULD_NOT_BIND_CHANGELOG, getReplicationPort(), e.getMessage());
439    }
440  }
441
442  /**
443   * Enable the external changelog if it is not already enabled.
444   * <p>
445   * The external changelog is provided by the changelog backend.
446   *
447   * @throws ConfigException
448   *            If an error occurs.
449   */
450  private void enableExternalChangeLog() throws ConfigException
451  {
452    if (DirectoryServer.hasBackend(ChangelogBackend.BACKEND_ID))
453    {
454      // Backend has already been created and initialized
455      // This can occurs in tests
456      return;
457    }
458    try
459    {
460      changelogBackend = new ChangelogBackend(this, domainPredicate);
461      changelogBackend.openBackend();
462      try
463      {
464        DirectoryServer.registerBackend(changelogBackend);
465      }
466      catch (Exception e)
467      {
468        logger.error(WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(changelogBackend.getBackendID(),
469            getExceptionMessage(e)));
470      }
471
472      registerVirtualAttributeRules();
473    }
474    catch (Exception e)
475    {
476      // TODO : I18N with correct message + what kind of exception should we really throw ?
477      // (Directory/Initialization/Config Exception)
478      throw new ConfigException(LocalizableMessage.raw("Error when enabling external changelog"), e);
479    }
480  }
481
482  private void shutdownExternalChangelog()
483  {
484    if (changelogBackend != null)
485    {
486      DirectoryServer.deregisterBackend(changelogBackend);
487      changelogBackend.finalizeBackend();
488      changelogBackend = null;
489    }
490    deregisterVirtualAttributeRules();
491  }
492
493  private List<VirtualAttributeRule> getVirtualAttributesRules() throws DirectoryException
494  {
495    final List<VirtualAttributeRule> rules = new ArrayList<>();
496    rules.add(buildVirtualAttributeRule("lastexternalchangelogcookie", new LastCookieVirtualProvider(this)));
497    rules.add(buildVirtualAttributeRule("firstchangenumber", new FirstChangeNumberVirtualAttributeProvider(this)));
498    rules.add(buildVirtualAttributeRule("lastchangenumber", new LastChangeNumberVirtualAttributeProvider(this)));
499    rules.add(buildVirtualAttributeRule("changelog", new ChangelogBaseDNVirtualAttributeProvider()));
500    return rules;
501  }
502
503  private void registerVirtualAttributeRules() throws DirectoryException {
504    for (VirtualAttributeRule rule : getVirtualAttributesRules())
505    {
506      DirectoryServer.registerVirtualAttribute(rule);
507    }
508  }
509
510  private void deregisterVirtualAttributeRules()
511  {
512    try
513    {
514      for (VirtualAttributeRule rule : getVirtualAttributesRules())
515      {
516        DirectoryServer.deregisterVirtualAttribute(rule);
517      }
518    }
519    catch (DirectoryException e)
520    {
521      // Should never happen
522      throw new RuntimeException(e);
523    }
524  }
525
526  private static VirtualAttributeRule buildVirtualAttributeRule(String attrName,
527      VirtualAttributeProvider<UserDefinedVirtualAttributeCfg> provider)
528      throws DirectoryException
529  {
530    ConflictBehavior conflictBehavior = ConflictBehavior.VIRTUAL_OVERRIDES_REAL;
531
532    try
533    {
534      Set<DN> baseDNs = Collections.singleton(DN.valueOf(""));
535      Set<DN> groupDNs = Collections.emptySet();
536      Set<SearchFilter> filters = Collections.singleton(SearchFilter.objectClassPresent());
537
538      // To avoid the configuration in cn=config just
539      // create a rule and register it into the DirectoryServer
540      provider.initializeVirtualAttributeProvider(null);
541
542      AttributeType attributeType = DirectoryServer.getAttributeType(attrName);
543      return new VirtualAttributeRule(attributeType, provider,
544            baseDNs, SearchScope.BASE_OBJECT,
545            groupDNs, filters, conflictBehavior);
546    }
547    catch (Exception e)
548    {
549      LocalizableMessage message =
550        NOTE_ERR_UNABLE_TO_ENABLE_ECL_VIRTUAL_ATTR.get(attrName, e);
551      throw new DirectoryException(ResultCode.OPERATIONS_ERROR, message, e);
552    }
553  }
554
555  /**
556   * Get the ReplicationServerDomain associated to the base DN given in
557   * parameter.
558   *
559   * @param baseDN
560   *          The base DN for which the ReplicationServerDomain must be
561   *          returned.
562   * @return The ReplicationServerDomain associated to the base DN given in
563   *         parameter.
564   */
565  public ReplicationServerDomain getReplicationServerDomain(DN baseDN)
566  {
567    return getReplicationServerDomain(baseDN, false);
568  }
569
570  /** Returns the replicated domain DNs minus the provided set of excluded DNs. */
571  private Set<DN> getDomainDNs(Set<DN> excludedBaseDNs) throws DirectoryException
572  {
573    Set<DN> domains = null;
574    synchronized (baseDNs)
575    {
576      domains = new HashSet<>(baseDNs.keySet());
577    }
578    domains.removeAll(excludedBaseDNs);
579    return domains;
580  }
581
582  /**
583   * Validate that provided cookie is coherent with this replication server,
584   * when ignoring the provided set of DNs.
585   * <p>
586   * The cookie is coherent if and only if it exactly has the set of DNs corresponding to
587   * the replication domains, and the states in the cookie are not older than oldest states
588   * in the server.
589   *
590   * @param cookie
591   *            The multi domain state (cookie) to validate.
592   * @param ignoredBaseDNs
593   *            The set of DNs to ignore when validating
594   * @throws DirectoryException
595   *            If the cookie is not valid
596   */
597  public void validateCookie(MultiDomainServerState cookie, Set<DN> ignoredBaseDNs) throws DirectoryException
598  {
599    final Set<DN> activeDomains = getDNsOfActiveDomainsInServer(ignoredBaseDNs);
600    final Set<DN> cookieDomains = getDNsOfCookie(cookie);
601
602    checkNoUnknownDomainIsProvidedInCookie(cookie, activeDomains, cookieDomains);
603    checkCookieIsNotOutdated(cookie, activeDomains);
604  }
605
606  private Set<DN> getDNsOfCookie(MultiDomainServerState cookie)
607  {
608    final Set<DN> cookieDomains = new HashSet<>();
609    for (final DN dn : cookie)
610    {
611      cookieDomains.add(dn);
612    }
613    return cookieDomains;
614  }
615
616  private Set<DN> getDNsOfActiveDomainsInServer(final Set<DN> ignoredBaseDNs) throws DirectoryException
617  {
618    final Set<DN> activeDomains = new HashSet<>();
619    for (final DN dn : getDomainDNs(ignoredBaseDNs))
620    {
621      final ServerState lastServerState = getReplicationServerDomain(dn).getLatestServerState();
622      if (!lastServerState.isEmpty())
623      {
624         activeDomains.add(dn);
625      }
626    }
627    return activeDomains;
628  }
629
630  private void checkNoUnknownDomainIsProvidedInCookie(final MultiDomainServerState cookie, final Set<DN> activeDomains,
631      final Set<DN> cookieDomains) throws DirectoryException
632  {
633    if (!activeDomains.containsAll(cookieDomains))
634    {
635      final Set<DN> unknownCookieDomains = new HashSet<>(cookieDomains);
636      unknownCookieDomains.removeAll(activeDomains);
637      final StringBuilder currentStartingCookie = new StringBuilder();
638      for (DN domainDN : activeDomains) {
639        currentStartingCookie.append(domainDN).append(":").append(cookie.getServerState(domainDN)).append(";");
640      }
641      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
642          ERR_RESYNC_REQUIRED_UNKNOWN_DOMAIN_IN_PROVIDED_COOKIE.get(
643              unknownCookieDomains.toString(), currentStartingCookie));
644    }
645  }
646
647  private void checkCookieIsNotOutdated(final MultiDomainServerState cookie, final Set<DN> activeDomains)
648      throws DirectoryException
649  {
650    for (DN dn : activeDomains)
651    {
652      if (isCookieOutdatedForDomain(cookie, dn))
653      {
654        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
655            ERR_RESYNC_REQUIRED_TOO_OLD_DOMAIN_IN_PROVIDED_COOKIE.get(dn.toString()));
656      }
657    }
658  }
659
660  /** Check that provided cookie is not outdated compared to the oldest state of a domain. */
661  private boolean isCookieOutdatedForDomain(MultiDomainServerState cookie, DN domainDN)
662  {
663    final ServerState providedState = cookie.getServerState(domainDN);
664    if (providedState == null)
665    {
666      // missing domains do not invalidate a cookie.
667      // results will include all the changes of the missing domains
668      return false;
669    }
670    final ServerState domainOldestState = getReplicationServerDomain(domainDN).getOldestState();
671    for (final CSN oldestCsn : domainOldestState)
672    {
673      final CSN providedCsn = providedState.getCSN(oldestCsn.getServerId());
674      if (providedCsn != null && providedCsn.isOlderThan(oldestCsn))
675      {
676        return true;
677      }
678    }
679    return false;
680  }
681
682  /**
683   * Get the ReplicationServerDomain associated to the base DN given in
684   * parameter.
685   *
686   * @param baseDN The base DN for which the ReplicationServerDomain must be
687   * returned.
688   * @param create Specifies whether to create the ReplicationServerDomain if
689   *        it does not already exist.
690   * @return The ReplicationServerDomain associated to the base DN given in
691   *         parameter.
692   */
693  public ReplicationServerDomain getReplicationServerDomain(DN baseDN,
694      boolean create)
695  {
696    synchronized (baseDNs)
697    {
698      ReplicationServerDomain domain = baseDNs.get(baseDN);
699      if (domain == null && create) {
700        domain = new ReplicationServerDomain(baseDN, this);
701        baseDNs.put(baseDN, domain);
702      }
703      return domain;
704    }
705  }
706
707  /**
708   * Waits for connections to this ReplicationServer.
709   */
710  void waitConnections()
711  {
712    // Acquire a domain ticket and wait for a complete cycle of the connect
713    // thread.
714    final long myDomainTicket;
715    synchronized (connectThreadLock)
716    {
717      // Connect thread must be waiting.
718      synchronized (domainTicketLock)
719      {
720        // Determine the ticket which will be used in the next connect thread
721        // iteration.
722        myDomainTicket = domainTicket + 1;
723      }
724
725      // Wake up connect thread.
726      connectThreadLock.notify();
727    }
728
729    // Wait until the connect thread has processed next connect phase.
730    synchronized (domainTicketLock)
731    {
732      while (myDomainTicket > domainTicket && !shutdown.get())
733      {
734        try
735        {
736          // Wait with timeout so that we detect shutdown.
737          domainTicketLock.wait(500);
738        }
739        catch (InterruptedException e)
740        {
741          // Can't do anything with this.
742          Thread.currentThread().interrupt();
743        }
744      }
745    }
746  }
747
748  /**
749   * Shutdown the Replication Server service and all its connections.
750   */
751  public void shutdown()
752  {
753    localPorts.remove(getReplicationPort());
754
755    if (!shutdown.compareAndSet(false, true))
756    {
757      return;
758    }
759
760    // shutdown the connect thread
761    if (connectThread != null)
762    {
763      connectThread.interrupt();
764    }
765
766    // shutdown the listener thread
767    close(listenSocket);
768    if (listenThread != null)
769    {
770      listenThread.interrupt();
771    }
772
773    // shutdown all the replication domains
774    for (ReplicationServerDomain domain : getReplicationServerDomains())
775    {
776      domain.shutdown();
777    }
778
779    shutdownExternalChangelog();
780
781    try
782    {
783      this.changelogDB.shutdownDB();
784    }
785    catch (ChangelogException ignored)
786    {
787      logger.traceException(ignored);
788    }
789
790    // Remove this instance from the global instance list
791    allInstances.remove(this);
792  }
793
794  /**
795   * Retrieves the time after which changes must be deleted from the
796   * persistent storage (in milliseconds).
797   *
798   * @return  The time after which changes must be deleted from the
799   *          persistent storage (in milliseconds).
800   */
801  public long getPurgeDelay()
802  {
803    return this.config.getReplicationPurgeDelay() * 1000;
804  }
805
806  /**
807   * Check if the provided configuration is acceptable for add.
808   *
809   * @param configuration The configuration to check.
810   * @param unacceptableReasons When the configuration is not acceptable, this
811   *                            table is use to return the reasons why this
812   *                            configuration is not acceptable.
813   *
814   * @return true if the configuration is acceptable, false other wise.
815   */
816  public static boolean isConfigurationAcceptable(
817      ReplicationServerCfg configuration, List<LocalizableMessage> unacceptableReasons)
818  {
819    int port = configuration.getReplicationPort();
820
821    try
822    {
823      ServerSocket tmpSocket = new ServerSocket();
824      tmpSocket.bind(new InetSocketAddress(port));
825      tmpSocket.close();
826      return true;
827    }
828    catch (Exception e)
829    {
830      LocalizableMessage message = ERR_COULD_NOT_BIND_CHANGELOG.get(port, e.getMessage());
831      unacceptableReasons.add(message);
832      return false;
833    }
834  }
835
836  /** {@inheritDoc} */
837  @Override
838  public ConfigChangeResult applyConfigurationChange(
839      ReplicationServerCfg configuration)
840  {
841    final ConfigChangeResult ccr = new ConfigChangeResult();
842
843    // Some of those properties change don't need specific code.
844    // They will be applied for next connections. Some others have immediate effect
845    final Set<HostPort> oldRSAddresses = getConfiguredRSAddresses();
846
847    final ReplicationServerCfg oldConfig = this.config;
848    this.config = configuration;
849
850    disconnectRemovedReplicationServers(oldRSAddresses);
851
852    final long newPurgeDelay = config.getReplicationPurgeDelay();
853    if (newPurgeDelay != oldConfig.getReplicationPurgeDelay())
854    {
855      this.changelogDB.setPurgeDelay(getPurgeDelay());
856    }
857    final boolean computeCN = config.isComputeChangeNumber();
858    if (computeCN != oldConfig.isComputeChangeNumber())
859    {
860      try
861      {
862        this.changelogDB.setComputeChangeNumber(computeCN);
863      }
864      catch (ChangelogException e)
865      {
866        logger.traceException(e);
867        ccr.setResultCode(ResultCode.OPERATIONS_ERROR);
868      }
869    }
870
871    // changing the listen port requires to stop the listen thread
872    // and restart it.
873    if (getReplicationPort() != oldConfig.getReplicationPort())
874    {
875      stopListen = true;
876      try
877      {
878        listenSocket.close();
879        listenThread.join();
880        stopListen = false;
881
882        setServerURL();
883        listenSocket = new ServerSocket();
884        listenSocket.bind(new InetSocketAddress(getReplicationPort()));
885
886        listenThread = new ReplicationServerListenThread(this);
887        listenThread.start();
888      }
889      catch (IOException e)
890      {
891        logger.traceException(e);
892        logger.error(ERR_COULD_NOT_CLOSE_THE_SOCKET, e);
893      }
894      catch (InterruptedException e)
895      {
896        logger.traceException(e);
897        logger.error(ERR_COULD_NOT_STOP_LISTEN_THREAD, e);
898      }
899    }
900
901    // Update period value for monitoring publishers
902    if (oldConfig.getMonitoringPeriod() != config.getMonitoringPeriod())
903    {
904      for (ReplicationServerDomain domain : getReplicationServerDomains())
905      {
906        domain.updateMonitoringPeriod(config.getMonitoringPeriod());
907      }
908    }
909
910    // Changed the group id ?
911    if (config.getGroupId() != oldConfig.getGroupId())
912    {
913      // Have a new group id: Disconnect every servers.
914      for (ReplicationServerDomain domain : getReplicationServerDomains())
915      {
916        domain.stopAllServers(true);
917      }
918    }
919
920    // Set a potential new weight
921    if (oldConfig.getWeight() != config.getWeight())
922    {
923      // Broadcast the new weight the the whole topology. This will make some
924      // DSs reconnect (if needed) to other RSs according to the new weight of
925      // this RS.
926      broadcastConfigChange();
927    }
928
929    final String newDir = config.getReplicationDBDirectory();
930    if (newDir != null && !newDir.equals(oldConfig.getReplicationDBDirectory()))
931    {
932      ccr.setAdminActionRequired(true);
933    }
934    return ccr;
935  }
936
937  /**
938   * Try and set a sensible URL for this replication server. Since we are
939   * listening on all addresses there are a couple of potential candidates:
940   * <ol>
941   * <li>a matching server URL in the replication server's configuration,</li>
942   * <li>hostname local address.</li>
943   * </ol>
944   */
945  private void setServerURL() throws UnknownHostException
946  {
947    /*
948     * First try the set of configured replication servers to see if one of them
949     * is this replication server (this should always be the case).
950     */
951    for (HostPort rsAddress : getConfiguredRSAddresses())
952    {
953      /*
954       * No need validate the string format because the admin framework has
955       * already done it.
956       */
957      if (rsAddress.getPort() == getReplicationPort()
958          && rsAddress.isLocalAddress())
959      {
960        serverURL = rsAddress.toString();
961        return;
962      }
963    }
964
965    // Fall-back to the machine hostname.
966    final String host = InetAddress.getLocalHost().getHostName();
967    // Ensure correct formatting of IPv6 addresses by using a HostPort instance.
968    serverURL = new HostPort(host, getReplicationPort()).toString();
969  }
970
971  /**
972   * Broadcast a configuration change that just happened to the whole topology
973   * by sending a TopologyMsg to every entity in the topology.
974   */
975  private void broadcastConfigChange()
976  {
977    for (ReplicationServerDomain domain : getReplicationServerDomains())
978    {
979      domain.sendTopoInfoToAll();
980    }
981  }
982
983  /** {@inheritDoc} */
984  @Override
985  public boolean isConfigurationChangeAcceptable(
986      ReplicationServerCfg configuration, List<LocalizableMessage> unacceptableReasons)
987  {
988    return true;
989  }
990
991  /**
992   * Get the value of generationId for the replication replicationServerDomain
993   * associated with the provided baseDN.
994   *
995   * @param baseDN The baseDN of the replicationServerDomain.
996   * @return The value of the generationID.
997   */
998  public long getGenerationId(DN baseDN)
999  {
1000    final ReplicationServerDomain rsd = getReplicationServerDomain(baseDN);
1001    return rsd != null ? rsd.getGenerationId() : -1;
1002  }
1003
1004  /**
1005   * Get the serverId for this replication server.
1006   *
1007   * @return The value of the serverId.
1008   *
1009   */
1010  public int getServerId()
1011  {
1012    return this.config.getReplicationServerId();
1013  }
1014
1015  /**
1016   * Do what needed when the config object related to this replication server
1017   * is deleted from the server configuration.
1018   */
1019  public void remove()
1020  {
1021    if (logger.isTraceEnabled())
1022    {
1023      logger.trace("RS " + getMonitorInstanceName() + " starts removing");
1024    }
1025    shutdown();
1026  }
1027
1028  /**
1029   * Returns an iterator on the list of replicationServerDomain.
1030   * Returns null if none.
1031   * @return the iterator.
1032   */
1033  public Iterator<ReplicationServerDomain> getDomainIterator()
1034  {
1035    return getReplicationServerDomains().iterator();
1036  }
1037
1038  /**
1039   * Get the assured mode timeout.
1040   * <p>
1041   * It is the Timeout (in milliseconds) when waiting for acknowledgments.
1042   *
1043   * @return The assured mode timeout.
1044   */
1045  public long getAssuredTimeout()
1046  {
1047    return this.config.getAssuredTimeout();
1048  }
1049
1050  /**
1051   * Get The replication server group id.
1052   * @return The replication server group id.
1053   */
1054  public byte getGroupId()
1055  {
1056    return (byte) this.config.getGroupId();
1057  }
1058
1059  /**
1060   * Get the degraded status threshold value for status analyzer.
1061   * <p>
1062   * The degraded status threshold is the number of pending changes for a DS,
1063   * considered as threshold value to put the DS in DEGRADED_STATUS. If value is
1064   * 0, status analyzer is disabled.
1065   *
1066   * @return The degraded status threshold value for status analyzer.
1067   */
1068  public int getDegradedStatusThreshold()
1069  {
1070    return this.config.getDegradedStatusThreshold();
1071  }
1072
1073  /**
1074   * Get the monitoring publisher period value.
1075   * <p>
1076   * It is the number of milliseconds to wait before sending new monitoring
1077   * messages. If value is 0, monitoring publisher is disabled.
1078   *
1079   * @return the monitoring publisher period value.
1080   */
1081  public long getMonitoringPublisherPeriod()
1082  {
1083    return this.config.getMonitoringPeriod();
1084  }
1085
1086  /**
1087   * Compute the list of replication servers that are not any more connected to
1088   * this Replication Server and stop the corresponding handlers.
1089   *
1090   * @param oldRSAddresses
1091   *          the old list of configured replication servers addresses.
1092   */
1093  private void disconnectRemovedReplicationServers(Set<HostPort> oldRSAddresses)
1094  {
1095    final Collection<HostPort> serversToDisconnect = new ArrayList<>();
1096
1097    final Set<HostPort> newRSAddresses = getConfiguredRSAddresses();
1098    for (HostPort oldRSAddress : oldRSAddresses)
1099    {
1100      if (!newRSAddresses.contains(oldRSAddress))
1101      {
1102        serversToDisconnect.add(oldRSAddress);
1103      }
1104    }
1105
1106    if (serversToDisconnect.isEmpty())
1107    {
1108      return;
1109    }
1110
1111    for (ReplicationServerDomain domain: getReplicationServerDomains())
1112    {
1113      domain.stopReplicationServers(serversToDisconnect);
1114    }
1115  }
1116
1117  /**
1118   * Retrieves a printable name for this Replication Server Instance.
1119   *
1120   * @return A printable name for this Replication Server Instance.
1121   */
1122  public String getMonitorInstanceName()
1123  {
1124    return "Replication Server " + getReplicationPort() + " " + getServerId();
1125  }
1126
1127  /**
1128   * Retrieves the port used by this ReplicationServer.
1129   *
1130   * @return The port used by this ReplicationServer.
1131   */
1132  public int getReplicationPort()
1133  {
1134    return config.getReplicationPort();
1135  }
1136
1137  /**
1138   * Getter on the server URL.
1139   * @return the server URL.
1140   */
1141  public String getServerURL()
1142  {
1143    return this.serverURL;
1144  }
1145
1146  /**
1147   * WARNING : only use this methods for tests purpose.
1148   *
1149   * Add the Replication Server given as a parameter in the list
1150   * of local replication servers.
1151   *
1152   * @param server The server to be added.
1153   */
1154  public static void onlyForTestsAddlocalReplicationServer(String server)
1155  {
1156    localPorts.add(HostPort.valueOf(server).getPort());
1157  }
1158
1159  /**
1160   * WARNING : only use this methods for tests purpose.
1161   *
1162   * Clear the list of local Replication Servers
1163   *
1164   */
1165  public static void onlyForTestsClearLocalReplicationServerList()
1166  {
1167    localPorts.clear();
1168  }
1169
1170  /**
1171   * Returns {@code true} if the provided port is one of the ports that this
1172   * replication server is listening on.
1173   *
1174   * @param port
1175   *          The port to be checked.
1176   * @return {@code true} if the provided port is one of the ports that this
1177   *         replication server is listening on.
1178   */
1179  public static boolean isLocalReplicationServerPort(int port)
1180  {
1181    return localPorts.contains(port);
1182  }
1183
1184  /**
1185   * Get (or create) a handler on the {@link ChangeNumberIndexDB} for external
1186   * changelog.
1187   *
1188   * @return the handler.
1189   */
1190  ChangeNumberIndexDB getChangeNumberIndexDB()
1191  {
1192    return this.changelogDB.getChangeNumberIndexDB();
1193  }
1194
1195  /**
1196   * Returns the oldest change number in the change number index DB.
1197   *
1198   * @return the oldest change number in the change number index DB
1199   * @throws DirectoryException
1200   *           When a problem happens
1201   */
1202  public long getOldestChangeNumber() throws DirectoryException
1203  {
1204    try
1205    {
1206      final ChangeNumberIndexDB cnIndexDB = getChangeNumberIndexDB();
1207      final ChangeNumberIndexRecord oldestRecord = cnIndexDB.getOldestRecord();
1208      if (oldestRecord != null)
1209      {
1210        return oldestRecord.getChangeNumber();
1211      }
1212      // database is empty
1213      return cnIndexDB.getLastGeneratedChangeNumber();
1214    }
1215    catch (ChangelogException e)
1216    {
1217      throw new DirectoryException(ResultCode.OPERATIONS_ERROR, e);
1218    }
1219  }
1220
1221  /**
1222   * Returns the newest change number in the change number index DB.
1223   *
1224   * @return the newest change number in the change number index DB
1225   * @throws DirectoryException
1226   *           When a problem happens
1227   */
1228  public long getNewestChangeNumber() throws DirectoryException
1229  {
1230    try
1231    {
1232      final ChangeNumberIndexDB cnIndexDB = getChangeNumberIndexDB();
1233      final ChangeNumberIndexRecord newestRecord = cnIndexDB.getNewestRecord();
1234      if (newestRecord != null)
1235      {
1236        return newestRecord.getChangeNumber();
1237      }
1238      // database is empty
1239      return cnIndexDB.getLastGeneratedChangeNumber();
1240    }
1241    catch (ChangelogException e)
1242    {
1243      throw new DirectoryException(ResultCode.OPERATIONS_ERROR, e);
1244    }
1245  }
1246
1247  /**
1248   * Returns the newest cookie value.
1249   *
1250   * @param excludedBaseDNs
1251   *          The set of baseDNs excluded from ECL.
1252   * @return the newest cookie value.
1253   */
1254  public MultiDomainServerState getNewestECLCookie(Set<DN> excludedBaseDNs)
1255  {
1256    // Initialize start state for all running domains with empty state
1257    final MultiDomainServerState result = new MultiDomainServerState();
1258    for (ReplicationServerDomain rsDomain : getReplicationServerDomains())
1259    {
1260      if (!excludedBaseDNs.contains(rsDomain.getBaseDN()))
1261      {
1262        final ServerState latestDBServerState = rsDomain.getLatestServerState();
1263        if (!latestDBServerState.isEmpty())
1264        {
1265          result.replace(rsDomain.getBaseDN(), latestDBServerState);
1266        }
1267      }
1268    }
1269    return result;
1270  }
1271
1272  /**
1273   * Gets the weight affected to the replication server.
1274   * <p>
1275   * Each replication server of the topology has a weight. When combined
1276   * together, the weights of the replication servers of a same group can be
1277   * translated to a percentage that determines the quantity of directory
1278   * servers of the topology that should be connected to a replication server.
1279   * <p>
1280   * For instance imagine a topology with 3 replication servers (with the same
1281   * group id) with the following weights: RS1=1, RS2=1, RS3=2. This means that
1282   * RS1 should have 25% of the directory servers connected in the topology, RS2
1283   * 25%, and RS3 50%. This may be useful if the replication servers of the
1284   * topology have a different power and one wants to spread the load between
1285   * the replication servers according to their power.
1286   *
1287   * @return the weight
1288   */
1289  public int getWeight()
1290  {
1291    return this.config.getWeight();
1292  }
1293
1294  private Collection<ReplicationServerDomain> getReplicationServerDomains()
1295  {
1296    synchronized (baseDNs)
1297    {
1298      return new ArrayList<>(baseDNs.values());
1299    }
1300  }
1301
1302  /**
1303   * Returns the changelogDB.
1304   *
1305   * @return the changelogDB.
1306   */
1307  public ChangelogDB getChangelogDB()
1308  {
1309    return this.changelogDB;
1310  }
1311
1312  /**
1313   * Returns the synchronization object for shutdown of combined DS/RS instances.
1314   *
1315   * @return the synchronization object for shutdown of combined DS/RS instances.
1316   */
1317  DSRSShutdownSync getDSRSShutdownSync()
1318  {
1319    return dsrsShutdownSync;
1320  }
1321
1322  /**
1323   * Returns whether change-log indexing is enabled for this RS.
1324   * @return true if change-log indexing is enabled for this RS.
1325   */
1326  public boolean isChangeNumberEnabled()
1327  {
1328    return config.isComputeChangeNumber();
1329  }
1330
1331  /**
1332   * Returns whether the external change-log contains data from at least a domain.
1333   * @return whether the external change-log contains data from at least a domain
1334   */
1335  public boolean isECLEnabled()
1336  {
1337    return MultimasterReplication.isECLEnabled();
1338  }
1339
1340  /** {@inheritDoc} */
1341  @Override
1342  public String toString()
1343  {
1344    return "RS(" + getServerId() + ") on " + serverURL + ", domains="
1345        + baseDNs.keySet();
1346  }
1347
1348}