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}