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.common.ServerStatus.*; 021import static org.opends.server.replication.common.StatusMachine.*; 022import static org.opends.server.replication.protocol.ProtocolVersion.*; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Date; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Set; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.i18n.slf4j.LocalizedLogger; 033import org.forgerock.opendj.ldap.ResultCode; 034import org.opends.server.api.MonitorData; 035import org.opends.server.replication.common.AssuredMode; 036import org.opends.server.replication.common.DSInfo; 037import org.opends.server.replication.common.ServerState; 038import org.opends.server.replication.common.ServerStatus; 039import org.opends.server.replication.common.StatusMachine; 040import org.opends.server.replication.common.StatusMachineEvent; 041import org.opends.server.replication.protocol.ChangeStatusMsg; 042import org.opends.server.replication.protocol.ProtocolVersion; 043import org.opends.server.replication.protocol.ReplServerStartDSMsg; 044import org.opends.server.replication.protocol.ReplicationMsg; 045import org.opends.server.replication.protocol.ServerStartMsg; 046import org.opends.server.replication.protocol.Session; 047import org.opends.server.replication.protocol.StartMsg; 048import org.opends.server.replication.protocol.StartSessionMsg; 049import org.opends.server.replication.protocol.StopMsg; 050import org.opends.server.replication.protocol.TopologyMsg; 051import org.opends.server.types.DirectoryException; 052 053/** 054 * This class defines a server handler, which handles all interaction with a 055 * peer server (RS or DS). 056 */ 057public class DataServerHandler extends ServerHandler 058{ 059 060 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 061 062 /** 063 * Temporary generationId received in handshake/phase1, and used after 064 * handshake/phase2. 065 */ 066 private long tmpGenerationId; 067 068 /** Status of this DS (only used if this server handler represents a DS). */ 069 private ServerStatus status = ServerStatus.INVALID_STATUS; 070 071 /** Referrals URLs this DS is exporting. */ 072 private List<String> refUrls = new ArrayList<>(); 073 /** Assured replication enabled on DS or not. */ 074 private boolean assuredFlag; 075 /** DS assured mode (relevant if assured replication enabled). */ 076 private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE; 077 /** DS safe data level (relevant if assured mode is safe data). */ 078 private byte safeDataLevel = -1; 079 private Set<String> eclIncludes = new HashSet<>(); 080 private Set<String> eclIncludesForDeletes = new HashSet<>(); 081 082 /** 083 * Creates a new data server handler. 084 * @param session The session opened with the remote data server. 085 * @param queueSize The queue size. 086 * @param replicationServer The hosting RS. 087 * @param rcvWindowSize The receiving window size. 088 */ 089 public DataServerHandler( 090 Session session, 091 int queueSize, 092 ReplicationServer replicationServer, 093 int rcvWindowSize) 094 { 095 super(session, queueSize, replicationServer, rcvWindowSize); 096 } 097 098 /** 099 * Order the peer DS server to change his status or close the connection 100 * according to the requested new generation id. 101 * @param newGenId The new generation id to take into account 102 * @throws IOException If IO error occurred. 103 */ 104 public void changeStatusForResetGenId(long newGenId) throws IOException 105 { 106 StatusMachineEvent event = getStatusMachineEvent(newGenId); 107 if (event == null) 108 { 109 return; 110 } 111 112 if (event == StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT 113 && status == ServerStatus.FULL_UPDATE_STATUS) 114 { 115 // Prevent useless error message (full update status cannot lead to bad gen status) 116 logger.info(NOTE_BAD_GEN_ID_IN_FULL_UPDATE, replicationServer.getServerId(), 117 getBaseDN(), serverId, generationId, newGenId); 118 return; 119 } 120 121 changeStatus(event, "for reset gen id"); 122 } 123 124 private StatusMachineEvent getStatusMachineEvent(long newGenId) 125 { 126 if (newGenId == -1) 127 { 128 // The generation id is being made invalid, let's put the DS 129 // into BAD_GEN_ID_STATUS 130 return StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT; 131 } 132 if (newGenId != generationId) 133 { 134 // This server has a bad generation id compared to new reference one, 135 // let's put it into BAD_GEN_ID_STATUS 136 return StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT; 137 } 138 139 if (status != ServerStatus.BAD_GEN_ID_STATUS) 140 { 141 if (logger.isTraceEnabled()) 142 { 143 logger.trace("In RS " + replicationServer.getServerId() 144 + ", DS " + getServerId() + " for baseDN=" + getBaseDN() 145 + " has already generation id " + newGenId 146 + " so no ChangeStatusMsg sent to him."); 147 } 148 return null; 149 } 150 151 // This server has the good new reference generation id. 152 // Close connection with him to force his reconnection: DS will 153 // reconnect in NORMAL_STATUS or DEGRADED_STATUS. 154 155 if (logger.isTraceEnabled()) 156 { 157 logger.trace("In RS " + replicationServer.getServerId() 158 + ", closing connection to DS " + getServerId() + " for baseDN=" + getBaseDN() 159 + " to force reconnection as new local generationId" 160 + " and remote one match and DS is in bad gen id: " + newGenId); 161 } 162 163 // Connection closure must not be done calling RSD.stopHandler() as it 164 // would rewait the RSD lock that we already must have entering this 165 // method. This would lead to a reentrant lock which we do not want. 166 // So simply close the session, this will make the hang up appear 167 // after the reader thread that took the RSD lock releases it. 168 if (session != null 169 // V4 protocol introduced a StopMsg to properly close the 170 // connection between servers 171 && getProtocolVersion() >= ProtocolVersion.REPLICATION_PROTOCOL_V4) 172 { 173 try 174 { 175 session.publish(new StopMsg()); 176 } 177 catch (IOException ioe) 178 { 179 // Anyway, going to close session, so nothing to do 180 } 181 } 182 183 // NOT_CONNECTED_STATUS is the last one in RS session life: handler 184 // will soon disappear after this method call... 185 status = ServerStatus.NOT_CONNECTED_STATUS; 186 return null; 187 } 188 189 /** 190 * Change the status according to the event. 191 * 192 * @param event 193 * The event to be used for new status computation 194 * @return The new status of the DS 195 * @throws IOException 196 * When raised by the underlying session 197 */ 198 public ServerStatus changeStatus(StatusMachineEvent event) throws IOException 199 { 200 return changeStatus(event, "from status analyzer"); 201 } 202 203 private ServerStatus changeStatus(StatusMachineEvent event, String origin) 204 throws IOException 205 { 206 // Check state machine allows this new status (Sanity check) 207 ServerStatus newStatus = StatusMachine.computeNewStatus(status, event); 208 if (newStatus == ServerStatus.INVALID_STATUS) 209 { 210 logger.error(ERR_RS_CANNOT_CHANGE_STATUS, getBaseDN(), serverId, status, event); 211 // Only change allowed is from NORMAL_STATUS to DEGRADED_STATUS and vice 212 // versa. We may be trying to change the status while another status has 213 // just been entered: e.g a full update has just been engaged. 214 // In that case, just ignore attempt to change the status 215 return newStatus; 216 } 217 218 // Send message requesting to change the DS status 219 ChangeStatusMsg csMsg = new ChangeStatusMsg(newStatus, INVALID_STATUS); 220 221 if (logger.isTraceEnabled()) 222 { 223 logger.trace("In RS " + replicationServer.getServerId() 224 + " Sending change status " + origin + " to " + getServerId() 225 + " for baseDN=" + getBaseDN() + ":\n" + csMsg); 226 } 227 228 session.publish(csMsg); 229 230 status = newStatus; 231 232 return newStatus; 233 } 234 235 @Override 236 public MonitorData getMonitorData() 237 { 238 MonitorData attributes = super.getMonitorData(); 239 240 // Add the specific DS ones 241 attributes.add("replica", serverURL); 242 attributes.add("connected-to", replicationServer.getMonitorInstanceName()); 243 244 ReplicationDomainMonitorData md = replicationServerDomain.getDomainMonitorData(); 245 246 // Oldest missing update 247 long approxFirstMissingDate = md.getApproxFirstMissingDate(serverId); 248 if (approxFirstMissingDate > 0) 249 { 250 attributes.add("approx-older-change-not-synchronized", new Date(approxFirstMissingDate)); 251 attributes.add("approx-older-change-not-synchronized-millis", approxFirstMissingDate); 252 } 253 254 attributes.add("missing-changes", md.getMissingChanges(serverId)); 255 // Replication delay 256 attributes.add("approximate-delay", md.getApproxDelay(serverId)); 257 258 ServerState state = md.getLDAPServerState(serverId); 259 if (state != null) 260 { 261 attributes.add("server-state", state.toStringSet()); 262 } 263 264 return attributes; 265 } 266 267 @Override 268 public String getMonitorInstanceName() 269 { 270 return "Connected directory server DS(" + serverId + ") " + serverURL 271 + ",cn=" + replicationServerDomain.getMonitorInstanceName(); 272 } 273 274 /** 275 * Gets the status of the connected DS. 276 * @return The status of the connected DS. 277 */ 278 @Override 279 public ServerStatus getStatus() 280 { 281 return status; 282 } 283 284 @Override 285 public boolean isDataServer() 286 { 287 return true; 288 } 289 290 /** 291 * Process message of a remote server changing his status. 292 * @param csMsg The message containing the new status 293 * @return The new server status of the DS 294 */ 295 public ServerStatus processNewStatus(ChangeStatusMsg csMsg) 296 { 297 // Get the status the DS just entered 298 ServerStatus reqStatus = csMsg.getNewStatus(); 299 // Translate new status to a state machine event 300 StatusMachineEvent event = StatusMachineEvent.statusToEvent(reqStatus); 301 if (event == StatusMachineEvent.INVALID_EVENT) 302 { 303 logger.error(ERR_RS_INVALID_NEW_STATUS, reqStatus, getBaseDN(), serverId); 304 return ServerStatus.INVALID_STATUS; 305 } 306 307 // Check state machine allows this new status 308 ServerStatus newStatus = StatusMachine.computeNewStatus(status, event); 309 if (newStatus == ServerStatus.INVALID_STATUS) 310 { 311 logger.error(ERR_RS_CANNOT_CHANGE_STATUS, getBaseDN(), serverId, status, event); 312 return ServerStatus.INVALID_STATUS; 313 } 314 315 status = newStatus; 316 return status; 317 } 318 319 /** 320 * Processes a start message received from a remote data server. 321 * @param serverStartMsg The provided start message received. 322 * @return flag specifying whether the remote server requests encryption. 323 * @throws DirectoryException raised when an error occurs. 324 */ 325 public boolean processStartFromRemote(ServerStartMsg serverStartMsg) 326 throws DirectoryException 327 { 328 session 329 .setProtocolVersion(getCompatibleVersion(serverStartMsg.getVersion())); 330 tmpGenerationId = serverStartMsg.getGenerationId(); 331 serverId = serverStartMsg.getServerId(); 332 serverURL = serverStartMsg.getServerURL(); 333 groupId = serverStartMsg.getGroupId(); 334 heartbeatInterval = serverStartMsg.getHeartbeatInterval(); 335 336 // generic stuff 337 setBaseDNAndDomain(serverStartMsg.getBaseDN(), true); 338 setInitialServerState(serverStartMsg.getServerState()); 339 setSendWindowSize(serverStartMsg.getWindowSize()); 340 341 if (heartbeatInterval < 0) 342 { 343 heartbeatInterval = 0; 344 } 345 return serverStartMsg.getSSLEncryption(); 346 } 347 348 /** Send our own TopologyMsg to DS. */ 349 private TopologyMsg sendTopoToRemoteDS() throws IOException 350 { 351 TopologyMsg outTopoMsg = replicationServerDomain 352 .createTopologyMsgForDS(this.serverId); 353 sendTopoInfo(outTopoMsg); 354 return outTopoMsg; 355 } 356 357 /** 358 * Starts the handler from a remote ServerStart message received from 359 * the remote data server. 360 * @param inServerStartMsg The provided ServerStart message received. 361 */ 362 public void startFromRemoteDS(ServerStartMsg inServerStartMsg) 363 { 364 try 365 { 366 // initializations 367 localGenerationId = -1; 368 oldGenerationId = -100; 369 370 // processes the ServerStart message received 371 boolean sessionInitiatorSSLEncryption = 372 processStartFromRemote(inServerStartMsg); 373 374 /** 375 * Hack to be sure that if a server disconnects and reconnect, we 376 * let the reader thread see the closure and cleanup any reference 377 * to old connection. This must be done before taking the domain lock so 378 * that the reader thread has a chance to stop the handler. 379 * 380 * TODO: This hack should be removed and disconnection/reconnection 381 * properly dealt with. 382 */ 383 if (replicationServerDomain.getConnectedDSs() 384 .containsKey(inServerStartMsg.getServerId())) 385 { 386 try { 387 Thread.sleep(100); 388 } 389 catch(Exception e){ 390 abortStart(null); 391 return; 392 } 393 } 394 395 lockDomainNoTimeout(); 396 397 localGenerationId = replicationServerDomain.getGenerationId(); 398 oldGenerationId = localGenerationId; 399 400 if (replicationServerDomain.isAlreadyConnectedToDS(this)) 401 { 402 abortStart(null); 403 return; 404 } 405 406 try 407 { 408 StartMsg outStartMsg = sendStartToRemote(); 409 410 logStartHandshakeRCVandSND(inServerStartMsg, outStartMsg); 411 412 // The session initiator decides whether to use SSL. 413 // Until here session is encrypted then it depends on the negotiation 414 if (!sessionInitiatorSSLEncryption) 415 { 416 session.stopEncryption(); 417 } 418 419 // wait and process StartSessionMsg from remote RS 420 StartSessionMsg inStartSessionMsg = 421 waitAndProcessStartSessionFromRemoteDS(); 422 if (inStartSessionMsg == null) 423 { 424 // DS wants to properly close the connection (DS sent a StopMsg) 425 logStopReceived(); 426 abortStart(null); 427 return; 428 } 429 430 // Send our own TopologyMsg to remote DS 431 TopologyMsg outTopoMsg = sendTopoToRemoteDS(); 432 433 logStartSessionHandshake(inStartSessionMsg, outTopoMsg); 434 } 435 catch(IOException e) 436 { 437 LocalizableMessage errMessage = ERR_DS_DISCONNECTED_DURING_HANDSHAKE.get( 438 inServerStartMsg.getServerId(), replicationServer.getServerId()); 439 throw new DirectoryException(ResultCode.OTHER, errMessage); 440 } 441 catch (Exception e) 442 { 443 // We do not need to support DS V1 connection, we just accept RS V1 444 // connection: 445 // We just trash the message, log the event for debug purpose and close 446 // the connection 447 throw new DirectoryException(ResultCode.OTHER, null, null); 448 } 449 450 replicationServerDomain.register(this); 451 452 logger.debug(INFO_REPLICATION_SERVER_CONNECTION_FROM_DS, getReplicationServerId(), getServerId(), 453 replicationServerDomain.getBaseDN(), session.getReadableRemoteAddress()); 454 455 super.finalizeStart(); 456 } 457 catch(DirectoryException de) 458 { 459 abortStart(de.getMessageObject()); 460 } 461 catch(Exception e) 462 { 463 abortStart(null); 464 } 465 finally 466 { 467 releaseDomainLock(); 468 } 469 } 470 471 /** 472 * Sends a start message to the remote DS. 473 * 474 * @return The StartMsg sent. 475 * @throws IOException 476 * When an exception occurs. 477 */ 478 private StartMsg sendStartToRemote() throws IOException 479 { 480 final StartMsg startMsg; 481 482 // Before V4 protocol, we sent a ReplServerStartMsg 483 if (getProtocolVersion() < ProtocolVersion.REPLICATION_PROTOCOL_V4) 484 { 485 // Peer DS uses protocol < V4 : send it a ReplServerStartMsg 486 startMsg = createReplServerStartMsg(); 487 } 488 else 489 { 490 // Peer DS uses protocol V4 : send it a ReplServerStartDSMsg 491 startMsg = new ReplServerStartDSMsg(getReplicationServerId(), 492 getReplicationServerURL(), getBaseDN(), maxRcvWindow, 493 replicationServerDomain.getLatestServerState(), 494 localGenerationId, sslEncryption, getLocalGroupId(), 495 replicationServer.getDegradedStatusThreshold(), 496 replicationServer.getWeight(), 497 replicationServerDomain.getConnectedDSs().size()); 498 } 499 500 send(startMsg); 501 return startMsg; 502 } 503 504 /** 505 * Creates a DSInfo structure representing this remote DS. 506 * @return The DSInfo structure representing this remote DS 507 */ 508 public DSInfo toDSInfo() 509 { 510 return new DSInfo(serverId, serverURL, getReplicationServerId(), 511 generationId, status, assuredFlag, assuredMode, safeDataLevel, groupId, 512 refUrls, eclIncludes, eclIncludesForDeletes, getProtocolVersion()); 513 } 514 515 @Override 516 public String toString() 517 { 518 if (serverId != 0) 519 { 520 return "Replica DS(" + serverId + ") for domain \"" 521 + replicationServerDomain.getBaseDN() + "\""; 522 } 523 return "Unknown server"; 524 } 525 526 /** 527 * Wait receiving the StartSessionMsg from the remote DS and process it, or 528 * receiving a StopMsg to properly stop the handshake procedure. 529 * @return the startSessionMsg received or null DS sent a stop message to 530 * not finish the handshake. 531 * @throws Exception 532 */ 533 private StartSessionMsg waitAndProcessStartSessionFromRemoteDS() 534 throws Exception 535 { 536 ReplicationMsg msg = session.receive(); 537 538 if (msg instanceof StopMsg) 539 { 540 // DS wants to stop handshake (was just for handshake phase one for RS 541 // choice). Return null to make the session be terminated. 542 return null; 543 } else if (!(msg instanceof StartSessionMsg)) 544 { 545 LocalizableMessage message = LocalizableMessage.raw( 546 "Protocol error: StartSessionMsg required." + msg + " received."); 547 abortStart(message); 548 return null; 549 } 550 551 // Process StartSessionMsg sent by remote DS 552 StartSessionMsg startSessionMsg = (StartSessionMsg) msg; 553 554 this.status = startSessionMsg.getStatus(); 555 // Sanity check: is it a valid initial status? 556 if (!isValidInitialStatus(this.status)) 557 { 558 throw new DirectoryException(ResultCode.OTHER, 559 ERR_RS_INVALID_INIT_STATUS.get( this.status, getBaseDN(), serverId)); 560 } 561 562 this.refUrls = startSessionMsg.getReferralsURLs(); 563 this.assuredFlag = startSessionMsg.isAssured(); 564 this.assuredMode = startSessionMsg.getAssuredMode(); 565 this.safeDataLevel = startSessionMsg.getSafeDataLevel(); 566 this.eclIncludes = startSessionMsg.getEclIncludes(); 567 this.eclIncludesForDeletes = startSessionMsg.getEclIncludesForDeletes(); 568 569 /* 570 * If we have already a generationID set for the domain 571 * then 572 * if the connecting replica has not the same 573 * then it is degraded locally and notified by an error message 574 * else 575 * we set the generationID from the one received 576 * (unsaved yet on disk . will be set with the 1rst change 577 * received) 578 */ 579 generationId = tmpGenerationId; 580 if (localGenerationId > 0) 581 { 582 if (generationId != localGenerationId) 583 { 584 logger.warn(WARN_BAD_GENERATION_ID_FROM_DS, serverId, session.getReadableRemoteAddress(), 585 generationId, getBaseDN(), getReplicationServerId(), localGenerationId); 586 } 587 } 588 else 589 { 590 // We are an empty ReplicationServer 591 if (generationId > 0 && !getServerState().isEmpty()) 592 { 593 // If the LDAP server has already sent changes 594 // it is not expected to connect to an empty RS 595 logger.warn(WARN_BAD_GENERATION_ID_FROM_DS, serverId, session.getReadableRemoteAddress(), 596 generationId, getBaseDN(), getReplicationServerId(), localGenerationId); 597 } 598 else 599 { 600 // The local RS is not initialized - take the one received 601 // WARNING: Must be done before computing topo message to send 602 // to peer server as topo message must embed valid generation id 603 // for our server 604 oldGenerationId = replicationServerDomain.changeGenerationId(generationId); 605 } 606 } 607 return startSessionMsg; 608 } 609 610 /** 611 * Process message of a remote server changing his status. 612 * @param csMsg The message containing the new status 613 */ 614 public void receiveNewStatus(ChangeStatusMsg csMsg) 615 { 616 replicationServerDomain.processNewStatus(this, csMsg); 617 } 618}