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