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