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.ReplicationMessages.*;
020import static org.opends.server.replication.protocol.ProtocolVersion.*;
021
022import java.io.IOException;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.ldap.DN;
031import org.forgerock.opendj.ldap.ResultCode;
032import org.opends.server.api.MonitorData;
033import org.opends.server.replication.common.DSInfo;
034import org.opends.server.replication.common.RSInfo;
035import org.opends.server.replication.common.ServerState;
036import org.opends.server.replication.common.ServerStatus;
037import org.opends.server.replication.protocol.ProtocolVersion;
038import org.opends.server.replication.protocol.ReplServerStartMsg;
039import org.opends.server.replication.protocol.ReplicationMsg;
040import org.opends.server.replication.protocol.Session;
041import org.opends.server.replication.protocol.StopMsg;
042import org.opends.server.replication.protocol.TopologyMsg;
043import org.opends.server.types.DirectoryException;
044import org.opends.server.types.HostPort;
045
046/**
047 * This class defines a server handler, which handles all interaction with a
048 * peer replication server.
049 */
050public class ReplicationServerHandler extends ServerHandler
051{
052  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
053
054  /** Properties filled only if remote server is a RS. */
055  private String serverAddressURL;
056  /**
057   * This collection will contain as many elements as there are
058   * LDAP servers connected to the remote replication server.
059   */
060  private final Map<Integer, LightweightServerHandler> remoteDirectoryServers = new ConcurrentHashMap<>();
061
062  /**
063   * Starts this handler based on a start message received from remote server.
064   * @param inReplServerStartMsg The start msg provided by the remote server.
065   * @return Whether the remote server requires encryption or not.
066   * @throws DirectoryException When a problem occurs.
067   */
068  private boolean processStartFromRemote(
069        ReplServerStartMsg inReplServerStartMsg)
070        throws DirectoryException
071  {
072    try
073    {
074      short protocolVersion = getCompatibleVersion(inReplServerStartMsg
075          .getVersion());
076      session.setProtocolVersion(protocolVersion);
077      generationId = inReplServerStartMsg.getGenerationId();
078      serverId = inReplServerStartMsg.getServerId();
079      serverURL = inReplServerStartMsg.getServerURL();
080      serverAddressURL = toServerAddressURL(serverURL);
081      setBaseDNAndDomain(inReplServerStartMsg.getBaseDN(), false);
082      setInitialServerState(inReplServerStartMsg.getServerState());
083      setSendWindowSize(inReplServerStartMsg.getWindowSize());
084      if (protocolVersion > ProtocolVersion.REPLICATION_PROTOCOL_V1)
085      {
086        // We support connection from a V1 RS
087        // Only V2 protocol has the group id in repl server start message
088        this.groupId = inReplServerStartMsg.getGroupId();
089      }
090
091      oldGenerationId = -100;
092    }
093    catch(Exception e)
094    {
095      LocalizableMessage message = LocalizableMessage.raw(e.getLocalizedMessage());
096      throw new DirectoryException(ResultCode.OTHER, message);
097    }
098    return inReplServerStartMsg.getSSLEncryption();
099  }
100
101  private String toServerAddressURL(String serverURL)
102  {
103    final int port = HostPort.valueOf(serverURL).getPort();
104    // Ensure correct formatting of IPv6 addresses by using a HostPort instance.
105    return new HostPort(session.getRemoteAddress(), port).toString();
106  }
107
108  /**
109   * Sends a start message to the remote RS.
110   *
111   * @return The ReplServerStartMsg sent.
112   * @throws IOException
113   *           When an exception occurs.
114   */
115  private ReplServerStartMsg sendStartToRemote() throws IOException
116  {
117    ReplServerStartMsg outReplServerStartMsg = createReplServerStartMsg();
118    send(outReplServerStartMsg);
119    return outReplServerStartMsg;
120  }
121
122  /**
123   * Creates a new handler object to a remote replication server.
124   * @param session The session with the remote RS.
125   * @param queueSize The queue size to manage updates to that RS.
126   * @param replicationServer The hosting local RS object.
127   * @param rcvWindowSize The receiving window size.
128   */
129  public ReplicationServerHandler(
130      Session session,
131      int queueSize,
132      ReplicationServer replicationServer,
133      int rcvWindowSize)
134  {
135    super(session, queueSize, replicationServer, rcvWindowSize);
136  }
137
138  /**
139   * Connect the hosting RS to the RS represented by THIS handler
140   * on an outgoing connection.
141   * @param baseDN The baseDN
142   * @param sslEncryption The sslEncryption requested to the remote RS.
143   * @throws DirectoryException when an error occurs.
144   */
145  public void connect(DN baseDN, boolean sslEncryption)
146      throws DirectoryException
147  {
148    // we are the initiator and decides of the encryption
149    this.sslEncryption = sslEncryption;
150
151    setBaseDNAndDomain(baseDN, false);
152
153    localGenerationId = replicationServerDomain.getGenerationId();
154    oldGenerationId = localGenerationId;
155
156    try
157    {
158      lockDomainNoTimeout();
159
160      ReplServerStartMsg outReplServerStartMsg = sendStartToRemote();
161
162      // Wait answer
163      ReplicationMsg msg = session.receive();
164
165      // Reject bad responses
166      if (!(msg instanceof ReplServerStartMsg))
167      {
168        if (msg instanceof StopMsg)
169        {
170          // Remote replication server is probably shutting down or simultaneous
171          // cross-connect detected.
172          abortStart(null);
173        }
174        else
175        {
176          LocalizableMessage message = ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(msg
177              .getClass().getCanonicalName(), "ReplServerStartMsg");
178          abortStart(message);
179        }
180        return;
181      }
182
183      processStartFromRemote((ReplServerStartMsg) msg);
184
185      if (replicationServerDomain.isAlreadyConnectedToRS(this))
186      {
187        // Simultaneous cross connect.
188        abortStart(null);
189        return;
190      }
191
192      /*
193      Since we are going to send the topology message before having received
194      one, we need to set the generation ID as soon as possible if it is
195      currently uninitialized. See OpenDJ-121.
196      */
197      if (localGenerationId < 0 && generationId > 0)
198      {
199        oldGenerationId =
200            replicationServerDomain.changeGenerationId(generationId);
201      }
202
203      logStartHandshakeSNDandRCV(outReplServerStartMsg,(ReplServerStartMsg)msg);
204
205      // Until here session is encrypted then it depends on the negotiation
206      // The session initiator decides whether to use SSL.
207      if (!this.sslEncryption)
208      {
209        session.stopEncryption();
210      }
211
212      if (getProtocolVersion() > ProtocolVersion.REPLICATION_PROTOCOL_V1)
213      {
214        /*
215        Only protocol version above V1 has a phase 2 handshake
216        NOW PROCEED WITH SECOND PHASE OF HANDSHAKE:
217        TopologyMsg then TopologyMsg (with a RS)
218
219        Send our own TopologyMsg to remote RS
220        */
221        TopologyMsg outTopoMsg =
222            replicationServerDomain.createTopologyMsgForRS();
223        sendTopoInfo(outTopoMsg);
224
225        // wait and process Topo from remote RS
226        TopologyMsg inTopoMsg = waitAndProcessTopoFromRemoteRS();
227        if (inTopoMsg == null)
228        {
229          // Simultaneous cross connect.
230          abortStart(null);
231          return;
232        }
233
234        logTopoHandshakeSNDandRCV(outTopoMsg, inTopoMsg);
235
236        /*
237        FIXME: i think this should be done for all protocol version !!
238        not only those > V1
239        */
240        replicationServerDomain.register(this);
241
242        /*
243        Process TopologyMsg sent by remote RS: store matching new info
244        (this will also warn our connected DSs of the new received info)
245        */
246        replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
247      }
248
249      logger.debug(INFO_REPLICATION_SERVER_CONNECTION_TO_RS, getReplicationServerId(), getServerId(),
250          replicationServerDomain.getBaseDN(), session.getReadableRemoteAddress());
251
252      super.finalizeStart();
253    }
254    catch (IOException e)
255    {
256      logger.traceException(e);
257      LocalizableMessage errMessage = ERR_RS_DISCONNECTED_DURING_HANDSHAKE.get(
258          getReplicationServerId(),
259          session.getReadableRemoteAddress());
260      abortStart(errMessage);
261    }
262    catch (DirectoryException e)
263    {
264      logger.traceException(e);
265      abortStart(e.getMessageObject());
266    }
267    catch (Exception e)
268    {
269      logger.traceException(e);
270      abortStart(LocalizableMessage.raw(e.getLocalizedMessage()));
271    }
272    finally
273    {
274      releaseDomainLock();
275    }
276  }
277
278  /**
279   * Starts the handler from a remote ReplServerStart message received from
280   * the remote replication server.
281   * @param inReplServerStartMsg The provided ReplServerStart message received.
282   */
283  public void startFromRemoteRS(ReplServerStartMsg inReplServerStartMsg)
284  {
285    localGenerationId = -1;
286    oldGenerationId = -100;
287    try
288    {
289      // The initiator decides if the session is encrypted
290      sslEncryption = processStartFromRemote(inReplServerStartMsg);
291
292      lockDomainWithTimeout();
293
294      if (replicationServerDomain.isAlreadyConnectedToRS(this))
295      {
296        abortStart(null);
297        return;
298      }
299
300      this.localGenerationId = replicationServerDomain.getGenerationId();
301      ReplServerStartMsg outReplServerStartMsg = sendStartToRemote();
302
303      logStartHandshakeRCVandSND(inReplServerStartMsg, outReplServerStartMsg);
304
305      /*
306      until here session is encrypted then it depends on the negotiation
307      The session initiator decides whether to use SSL.
308      */
309      if (!sslEncryption)
310      {
311        session.stopEncryption();
312      }
313
314      TopologyMsg inTopoMsg = null;
315      if (getProtocolVersion() > ProtocolVersion.REPLICATION_PROTOCOL_V1)
316      {
317        /*
318        Only protocol version above V1 has a phase 2 handshake
319        NOW PROCEED WITH SECOND PHASE OF HANDSHAKE:
320        TopologyMsg then TopologyMsg (with a RS)
321        wait and process Topo from remote RS
322        */
323        inTopoMsg = waitAndProcessTopoFromRemoteRS();
324        if (inTopoMsg == null)
325        {
326          // Simultaneous cross connect.
327          abortStart(null);
328          return;
329        }
330
331        // send our own TopologyMsg to remote RS
332        TopologyMsg outTopoMsg = replicationServerDomain
333            .createTopologyMsgForRS();
334        sendTopoInfo(outTopoMsg);
335
336        logTopoHandshakeRCVandSND(inTopoMsg, outTopoMsg);
337      }
338      else
339      {
340        // Terminate connection from a V1 RS
341
342        // if the remote RS and the local RS have the same genID
343        // then it's ok and nothing else to do
344        if (generationId == localGenerationId)
345        {
346          if (logger.isTraceEnabled())
347          {
348            logger.trace("In " + replicationServer.getMonitorInstanceName()
349                + " " + this + " RS V1 with serverID=" + serverId
350                + " is connected with the right generation ID");
351          }
352        } else
353        {
354          checkGenerationId();
355        }
356        /*
357        Note: the supported scenario for V1->V2 upgrade is to upgrade 1 by 1
358        all the servers of the topology. We prefer not not send a TopologyMsg
359        for giving partial/false information to the V2 servers as for
360        instance we don't have the connected DS of the V1 RS...When the V1
361        RS will be upgraded in his turn, topo info will be sent and accurate.
362        That way, there is  no risk to have false/incomplete information in
363        other servers.
364        */
365      }
366
367      replicationServerDomain.register(this);
368
369      // Process TopologyMsg sent by remote RS: store matching new info
370      // (this will also warn our connected DSs of the new received info)
371      if (inTopoMsg!=null)
372      {
373        replicationServerDomain.receiveTopoInfoFromRS(inTopoMsg, this, false);
374      }
375
376      logger.debug(INFO_REPLICATION_SERVER_CONNECTION_FROM_RS, getReplicationServerId(), getServerId(),
377          replicationServerDomain.getBaseDN(), session.getReadableRemoteAddress());
378
379      super.finalizeStart();
380    }
381    catch (IOException e)
382    {
383      logger.traceException(e);
384      abortStart(ERR_RS_DISCONNECTED_DURING_HANDSHAKE.get(
385          inReplServerStartMsg.getServerId(), replicationServer.getServerId()));
386    }
387    catch (DirectoryException e)
388    {
389      logger.traceException(e);
390      abortStart(e.getMessageObject());
391    }
392    catch (Exception e)
393    {
394      logger.traceException(e);
395      abortStart(LocalizableMessage.raw(e.getLocalizedMessage()));
396    }
397    finally
398    {
399      releaseDomainLock();
400    }
401  }
402
403  /**
404   * Wait receiving the TopologyMsg from the remote RS and process it.
405   * @return the topologyMsg received or {@code null} if stop was received.
406   * @throws DirectoryException
407   */
408  private TopologyMsg waitAndProcessTopoFromRemoteRS()
409      throws DirectoryException
410  {
411    ReplicationMsg msg;
412    try
413    {
414      msg = session.receive();
415    }
416    catch(Exception e)
417    {
418      LocalizableMessage message = LocalizableMessage.raw(e.getLocalizedMessage());
419      throw new DirectoryException(ResultCode.OTHER, message);
420    }
421
422    if (!(msg instanceof TopologyMsg))
423    {
424      if (msg instanceof StopMsg)
425      {
426        // Remote replication server is probably shutting down, or cross
427        // connection attempt.
428        return null;
429      }
430
431      LocalizableMessage message = ERR_REPLICATION_PROTOCOL_MESSAGE_TYPE.get(
432          msg.getClass().getCanonicalName(), "TopologyMsg");
433      throw new DirectoryException(ResultCode.OTHER, message);
434    }
435
436    // Remote RS sent his topo msg
437    TopologyMsg inTopoMsg = (TopologyMsg) msg;
438
439    /* Store remote RS weight if it has one.
440     * For protocol version < 4, use default value of 1 for weight
441     */
442    if (getProtocolVersion() >= ProtocolVersion.REPLICATION_PROTOCOL_V4)
443    {
444      // List should only contain RS info for sender
445      RSInfo rsInfo = inTopoMsg.getRsInfos().get(0);
446      weight = rsInfo.getWeight();
447    }
448
449    /*
450    if the remote RS and the local RS have the same genID
451    then it's ok and nothing else to do
452    */
453    if (generationId == localGenerationId)
454    {
455      if (logger.isTraceEnabled())
456      {
457        logger.trace("In " + replicationServer.getMonitorInstanceName()
458            + " RS with serverID=" + serverId
459            + " is connected with the right generation ID, same as local ="
460            + generationId);
461      }
462    }
463    else
464    {
465      checkGenerationId();
466    }
467
468    return inTopoMsg;
469  }
470
471  /**
472   * Checks local generation ID against the remote RS one,
473   * and logs Warning messages if needed.
474   */
475  private void checkGenerationId()
476  {
477    if (localGenerationId <= 0)
478    {
479      // The local RS is not initialized - take the one received
480      // WARNING: Must be done before computing topo message to send to peer
481      // server as topo message must embed valid generation id for our server
482      oldGenerationId =
483          replicationServerDomain.changeGenerationId(generationId);
484      return;
485    }
486
487    // the local RS is initialized
488    if (generationId > 0
489        // the remote RS is initialized. If not, there's nothing to do anyway.
490        && generationId != localGenerationId)
491    {
492      /* Either:
493       *
494       * 1) The 2 RS have different generationID
495       * replicationServerDomain.getGenerationIdSavedStatus() == true
496       *
497       * if the present RS has received changes regarding its gen ID and so will
498       * not change without a reset then we are just degrading the peer.
499       *
500       * 2) This RS has never received any changes for the current gen ID.
501       *
502       * Example case:
503       * - we are in RS1
504       * - RS2 has genId2 from LS2 (genId2 <=> no data in LS2)
505       * - RS1 has genId1 from LS1 /genId1 comes from data in suffix
506       * - we are in RS1 and we receive a START msg from RS2
507       * - Each RS keeps its genID / is degraded and when LS2
508       * will be populated from LS1 everything will become ok.
509       *
510       * Issue:
511       * FIXME : Would it be a good idea in some cases to just set the gen ID
512       * received from the peer RS specially if the peer has a non null state
513       * and we have a null state ?
514       * replicationServerDomain.setGenerationId(generationId, false);
515       */
516      logger.warn(WARN_BAD_GENERATION_ID_FROM_RS, serverId, session.getReadableRemoteAddress(), generationId,
517          getBaseDN(), getReplicationServerId(), localGenerationId);
518    }
519  }
520
521  /** {@inheritDoc} */
522  @Override
523  public boolean isDataServer()
524  {
525    return false;
526  }
527
528  /**
529   * Add the DSinfos of the connected Directory Servers
530   * to the List of DSInfo provided as a parameter.
531   *
532   * @param dsInfos The List of DSInfo that should be updated
533   *                with the DSInfo for the remoteDirectoryServers
534   *                connected to this ServerHandler.
535   */
536  public void addDSInfos(List<DSInfo> dsInfos)
537  {
538    synchronized (remoteDirectoryServers)
539    {
540      for (LightweightServerHandler ls : remoteDirectoryServers.values())
541      {
542        dsInfos.add(ls.toDSInfo());
543      }
544    }
545  }
546
547  /**
548   * Shutdown This ServerHandler.
549   */
550  @Override
551  public void shutdown()
552  {
553    super.shutdown();
554    clearRemoteLSHandlers();
555  }
556
557  private void clearRemoteLSHandlers()
558  {
559    synchronized (remoteDirectoryServers)
560    {
561      for (LightweightServerHandler lsh : remoteDirectoryServers.values())
562      {
563        lsh.stopHandler();
564      }
565      remoteDirectoryServers.clear();
566    }
567  }
568
569  /**
570   * Stores topology information received from a peer RS and that must be kept
571   * in RS handler.
572   *
573   * @param topoMsg The received topology message
574   */
575  public void processTopoInfoFromRS(TopologyMsg topoMsg)
576  {
577    // List should only contain RS info for sender
578    final RSInfo rsInfo = topoMsg.getRsInfos().get(0);
579    generationId = rsInfo.getGenerationId();
580    groupId = rsInfo.getGroupId();
581    weight = rsInfo.getWeight();
582
583    synchronized (remoteDirectoryServers)
584    {
585      clearRemoteLSHandlers();
586
587      // Creates the new structure according to the message received.
588      for (DSInfo dsInfo : topoMsg.getReplicaInfos().values())
589      {
590        // For each DS connected to the peer RS
591        DSInfo clonedDSInfo = dsInfo.cloneWithReplicationServerId(serverId);
592        LightweightServerHandler lsh =
593            new LightweightServerHandler(this, clonedDSInfo);
594        lsh.startHandler();
595        remoteDirectoryServers.put(lsh.getServerId(), lsh);
596      }
597    }
598  }
599
600  /**
601   * When this handler is connected to a replication server, specifies if
602   * a wanted server is connected to this replication server.
603   *
604   * @param serverId The server we want to know if it is connected
605   * to the replication server represented by this handler.
606   * @return boolean True is the wanted server is connected to the server
607   * represented by this handler.
608   */
609  public boolean isRemoteLDAPServer(int serverId)
610  {
611    synchronized (remoteDirectoryServers)
612    {
613      for (LightweightServerHandler server : remoteDirectoryServers.values())
614      {
615        if (serverId == server.getServerId())
616        {
617          return true;
618        }
619      }
620      return false;
621    }
622  }
623
624  /**
625   * When the handler is connected to a replication server, specifies the
626   * replication server has remote LDAP servers connected to it.
627   *
628   * @return boolean True is the replication server has remote LDAP servers
629   * connected to it.
630   */
631  public boolean hasRemoteLDAPServers()
632  {
633    return !remoteDirectoryServers.isEmpty();
634  }
635
636  /**
637   * Return a Set containing the servers known by this replicationServer.
638   * @return a set containing the servers known by this replicationServer.
639   */
640  public Set<Integer> getConnectedDirectoryServerIds()
641  {
642    return remoteDirectoryServers.keySet();
643  }
644
645  /** {@inheritDoc} */
646  @Override
647  public String getMonitorInstanceName()
648  {
649    return "Connected replication server RS(" + serverId + ") " + serverURL
650        + ",cn=" + replicationServerDomain.getMonitorInstanceName();
651  }
652
653  @Override
654  public MonitorData getMonitorData()
655  {
656    MonitorData attributes = super.getMonitorData();
657
658    ReplicationDomainMonitorData md = replicationServerDomain.getDomainMonitorData();
659    attributes.add("Replication-Server", serverURL);
660    attributes.add("missing-changes", md.getMissingChangesRS(serverId));
661
662    ServerState state = md.getRSStates(serverId);
663    if (state != null)
664    {
665      attributes.add("server-state", state.toStringSet());
666    }
667
668    return attributes;
669  }
670
671  /** {@inheritDoc} */
672  @Override
673  public String toString()
674  {
675    if (serverId != 0)
676    {
677      return "Replication server RS(" + serverId + ") for domain \""
678          + replicationServerDomain.getBaseDN() + "\"";
679    }
680    return "Unknown server";
681  }
682
683  /**
684   * Gets the status of the connected DS.
685   * @return The status of the connected DS.
686   */
687  @Override
688  public ServerStatus getStatus()
689  {
690    return ServerStatus.INVALID_STATUS;
691  }
692
693  /**
694   * Retrieves the Address URL for this server handler.
695   *
696   * @return  The Address URL for this server handler,
697   *          in the form of an IP address and port separated by a colon.
698   */
699  public String getServerAddressURL()
700  {
701    return serverAddressURL;
702  }
703
704  /**
705   * Receives a topology msg.
706   * @param topoMsg The message received.
707   * @throws DirectoryException when it occurs.
708   * @throws IOException when it occurs.
709   */
710  public void receiveTopoInfoFromRS(TopologyMsg topoMsg)
711  throws DirectoryException, IOException
712  {
713    replicationServerDomain.receiveTopoInfoFromRS(topoMsg, this, true);
714  }
715
716}