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 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2015 ForgeRock AS. 016 */ 017package org.opends.admin.ads; 018 019import java.util.Collection; 020import java.util.Date; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.Iterator; 024import java.util.LinkedHashSet; 025import java.util.Map; 026import java.util.Set; 027 028import javax.naming.NameNotFoundException; 029import javax.naming.NamingEnumeration; 030import javax.naming.NamingException; 031import javax.naming.directory.SearchControls; 032import javax.naming.directory.SearchResult; 033import javax.naming.ldap.InitialLdapContext; 034import javax.naming.ldap.LdapName; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.i18n.slf4j.LocalizedLogger; 038import org.opends.admin.ads.ADSContext.ServerProperty; 039import org.opends.admin.ads.util.ApplicationTrustManager; 040import org.opends.admin.ads.util.ConnectionUtils; 041import org.opends.admin.ads.util.PreferredConnection; 042import org.opends.admin.ads.util.ServerLoader; 043import org.opends.quicksetup.util.Utils; 044 045import static com.forgerock.opendj.cli.Utils.*; 046 047import static org.opends.messages.QuickSetupMessages.*; 048 049/** 050 * This class allows to read the configuration of the different servers that are 051 * registered in a given ADS server. It provides a read only view of the 052 * configuration of the servers and of the replication topologies that might be 053 * configured between them. 054 */ 055public class TopologyCache 056{ 057 058 private final ADSContext adsContext; 059 private final ApplicationTrustManager trustManager; 060 private final int timeout; 061 private final String bindDN; 062 private final String bindPwd; 063 private final Set<ServerDescriptor> servers = new HashSet<>(); 064 private final Set<SuffixDescriptor> suffixes = new HashSet<>(); 065 private final Set<PreferredConnection> preferredConnections = new LinkedHashSet<>(); 066 private final TopologyCacheFilter filter = new TopologyCacheFilter(); 067 private static final int MULTITHREAD_TIMEOUT = 90 * 1000; 068 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 069 070 /** 071 * Constructor of the TopologyCache. 072 * 073 * @param adsContext the adsContext to the ADS registry. 074 * @param trustManager the ApplicationTrustManager that must be used to trust 075 * certificates when we create connections to the registered servers to read 076 * their configuration. 077 * @param timeout the timeout to establish the connection in milliseconds. 078 * Use {@code 0} to express no timeout. 079 */ 080 public TopologyCache(ADSContext adsContext, 081 ApplicationTrustManager trustManager, 082 int timeout) 083 { 084 this.adsContext = adsContext; 085 this.trustManager = trustManager; 086 this.timeout = timeout; 087 bindDN = ConnectionUtils.getBindDN(adsContext.getDirContext()); 088 bindPwd = ConnectionUtils.getBindPassword(adsContext.getDirContext()); 089 } 090 091 /** 092 * Reads the configuration of the registered servers. 093 * 094 * @throws TopologyCacheException if there is an issue reading the 095 * configuration of the registered servers. 096 */ 097 public void reloadTopology() throws TopologyCacheException 098 { 099 suffixes.clear(); 100 servers.clear(); 101 try 102 { 103 Set<Map<ServerProperty, Object>> adsServers = 104 adsContext.readServerRegistry(); 105 106 Set<ServerLoader> threadSet = new HashSet<>(); 107 for (Map<ServerProperty, Object> serverProperties : adsServers) 108 { 109 ServerLoader t = getServerLoader(serverProperties); 110 t.start(); 111 threadSet.add(t); 112 } 113 joinThreadSet(threadSet); 114 /* 115 * Try to consolidate things (even if the data is not complete). 116 */ 117 118 HashMap<LdapName, Set<SuffixDescriptor>> hmSuffixes = new HashMap<>(); 119 for (ServerLoader loader : threadSet) 120 { 121 ServerDescriptor descriptor = loader.getServerDescriptor(); 122 for (ReplicaDescriptor replica : descriptor.getReplicas()) 123 { 124 logger.info(LocalizableMessage.raw("Handling replica with dn: " 125 + replica.getSuffix().getDN())); 126 127 boolean suffixFound = false; 128 LdapName dn = new LdapName(replica.getSuffix().getDN()); 129 Set<SuffixDescriptor> sufs = hmSuffixes.get(dn); 130 if (sufs != null) 131 { 132 Iterator<SuffixDescriptor> it = sufs.iterator(); 133 while (it.hasNext() && !suffixFound) 134 { 135 SuffixDescriptor suffix = it.next(); 136 Iterator<String> it2 = suffix.getReplicationServers().iterator(); 137 while (it2.hasNext() && !suffixFound) 138 { 139 if (replica.getReplicationServers().contains(it2.next())) 140 { 141 suffixFound = true; 142 Set<ReplicaDescriptor> replicas = suffix.getReplicas(); 143 replicas.add(replica); 144 suffix.setReplicas(replicas); 145 replica.setSuffix(suffix); 146 } 147 } 148 } 149 } 150 if (!suffixFound) 151 { 152 if (sufs == null) 153 { 154 sufs = new HashSet<>(); 155 hmSuffixes.put(dn, sufs); 156 } 157 sufs.add(replica.getSuffix()); 158 suffixes.add(replica.getSuffix()); 159 } 160 } 161 servers.add(descriptor); 162 } 163 164 // Figure out the replication monitoring if it is required. 165 if (getFilter().searchMonitoringInformation()) 166 { 167 readReplicationMonitoring(); 168 } 169 } 170 catch (ADSContextException ade) 171 { 172 throw new TopologyCacheException(ade); 173 } 174 catch (Throwable t) 175 { 176 throw new TopologyCacheException(TopologyCacheException.Type.BUG, t); 177 } 178 } 179 180 /** 181 * Returns the trust manager used by this class. 182 * 183 * @return the trust manager used by this class. 184 */ 185 public ApplicationTrustManager getTrustManager() 186 { 187 return trustManager; 188 } 189 190 /** 191 * Returns the timeout to establish the connection in milliseconds. 192 * 193 * @return the timeout to establish the connection in milliseconds. Returns 194 * {@code 0} to express no timeout. 195 */ 196 public int getConnectTimeout() 197 { 198 return timeout; 199 } 200 201 /** 202 * Reads the replication monitoring. 203 */ 204 private void readReplicationMonitoring() 205 { 206 Set<ReplicaDescriptor> replicasToUpdate = getReplicasToUpdate(); 207 for (ServerDescriptor server : getServers()) 208 { 209 if (server.isReplicationServer()) 210 { 211 // If is replication server, then at least we were able to read the 212 // configuration, so assume that we might be able to read monitoring 213 // (even if an exception occurred before). 214 Set<ReplicaDescriptor> candidateReplicas = getCandidateReplicas(server); 215 if (!candidateReplicas.isEmpty()) 216 { 217 Set<ReplicaDescriptor> updatedReplicas = new HashSet<>(); 218 try 219 { 220 updateReplicas(server, candidateReplicas, updatedReplicas); 221 } 222 catch (NamingException ne) 223 { 224 server.setLastException(new TopologyCacheException( 225 TopologyCacheException.Type.GENERIC_READING_SERVER, ne)); 226 } 227 replicasToUpdate.removeAll(updatedReplicas); 228 } 229 } 230 231 if (replicasToUpdate.isEmpty()) 232 { 233 break; 234 } 235 } 236 } 237 238 private Set<ReplicaDescriptor> getReplicasToUpdate() 239 { 240 Set<ReplicaDescriptor> replicasToUpdate = new HashSet<>(); 241 for (ServerDescriptor server : getServers()) 242 { 243 for (ReplicaDescriptor replica : server.getReplicas()) 244 { 245 if (replica.isReplicated()) 246 { 247 replicasToUpdate.add(replica); 248 } 249 } 250 } 251 return replicasToUpdate; 252 } 253 254 private Set<ReplicaDescriptor> getCandidateReplicas(ServerDescriptor server) 255 { 256 Set<ReplicaDescriptor> candidateReplicas = new HashSet<>(); 257 // It contains replication information: analyze it. 258 String repServer = server.getReplicationServerHostPort(); 259 for (SuffixDescriptor suffix : getSuffixes()) 260 { 261 if (containsIgnoreCase(suffix.getReplicationServers(), repServer)) 262 { 263 candidateReplicas.addAll(suffix.getReplicas()); 264 } 265 } 266 return candidateReplicas; 267 } 268 269 private boolean containsIgnoreCase(Set<String> col, String toFind) 270 { 271 for (String s : col) 272 { 273 if (s.equalsIgnoreCase(toFind)) 274 { 275 return true; 276 } 277 } 278 return false; 279 } 280 281 /** 282 * Sets the list of LDAP URLs and connection type that are preferred to be 283 * used to connect to the servers. When we have a server to which we can 284 * connect using a URL on the list we will try to use it. 285 * 286 * @param cnx the list of preferred connections. 287 */ 288 public void setPreferredConnections(Set<PreferredConnection> cnx) 289 { 290 preferredConnections.clear(); 291 preferredConnections.addAll(cnx); 292 } 293 294 /** 295 * Returns the list of LDAP URLs and connection type that are preferred to be 296 * used to connect to the servers. If a URL is on this list, when we have a 297 * server to which we can connect using that URL and the associated connection 298 * type we will try to use it. 299 * 300 * @return the list of preferred connections. 301 */ 302 public LinkedHashSet<PreferredConnection> getPreferredConnections() 303 { 304 return new LinkedHashSet<>(preferredConnections); 305 } 306 307 /** 308 * Returns a Set containing all the servers that are registered in the ADS. 309 * 310 * @return a Set containing all the servers that are registered in the ADS. 311 */ 312 public Set<ServerDescriptor> getServers() 313 { 314 return new HashSet<>(servers); 315 } 316 317 /** 318 * Returns a Set containing the suffixes (replication topologies) that could 319 * be retrieved after the last call to reloadTopology. 320 * 321 * @return a Set containing the suffixes (replication topologies) that could 322 * be retrieved after the last call to reloadTopology. 323 */ 324 public Set<SuffixDescriptor> getSuffixes() 325 { 326 return new HashSet<>(suffixes); 327 } 328 329 /** 330 * Returns the filter to be used when retrieving information. 331 * 332 * @return the filter to be used when retrieving information. 333 */ 334 public TopologyCacheFilter getFilter() 335 { 336 return filter; 337 } 338 339 /** 340 * Method used to wait at most a certain time (MULTITHREAD_TIMEOUT) for the 341 * different threads to finish. 342 * 343 * @param threadSet the list of threads (we assume that they are started) that 344 * we must wait for. 345 */ 346 private void joinThreadSet(Set<ServerLoader> threadSet) 347 { 348 Date startDate = new Date(); 349 for (ServerLoader t : threadSet) 350 { 351 long timeToJoin = MULTITHREAD_TIMEOUT - System.currentTimeMillis() 352 + startDate.getTime(); 353 try 354 { 355 if (timeToJoin > 0) 356 { 357 t.join(MULTITHREAD_TIMEOUT); 358 } 359 } 360 catch (InterruptedException ie) 361 { 362 logger.info(LocalizableMessage.raw(ie + " caught and ignored", ie)); 363 } 364 if (t.isAlive()) 365 { 366 t.interrupt(); 367 } 368 } 369 Date endDate = new Date(); 370 long workingTime = endDate.getTime() - startDate.getTime(); 371 logger.info(LocalizableMessage.raw("Loading ended at " + workingTime + " ms")); 372 } 373 374 /** 375 * Creates a ServerLoader object based on the provided server properties. 376 * 377 * @param serverProperties the server properties to be used to generate the 378 * ServerLoader. 379 * @return a ServerLoader object based on the provided server properties. 380 */ 381 private ServerLoader getServerLoader( 382 Map<ServerProperty, Object> serverProperties) 383 { 384 return new ServerLoader(serverProperties, bindDN, bindPwd, 385 trustManager == null ? null : trustManager.createCopy(), 386 timeout, 387 getPreferredConnections(), getFilter()); 388 } 389 390 /** 391 * Returns the adsContext used by this TopologyCache. 392 * 393 * @return the adsContext used by this TopologyCache. 394 */ 395 public ADSContext getAdsContext() 396 { 397 return adsContext; 398 } 399 400 /** 401 * Returns a set of error messages encountered in the TopologyCache. 402 * 403 * @return a set of error messages encountered in the TopologyCache. 404 */ 405 public Set<LocalizableMessage> getErrorMessages() 406 { 407 Set<TopologyCacheException> exceptions = new HashSet<>(); 408 Set<ServerDescriptor> theServers = getServers(); 409 Set<LocalizableMessage> exceptionMsgs = new LinkedHashSet<>(); 410 for (ServerDescriptor server : theServers) 411 { 412 TopologyCacheException e = server.getLastException(); 413 if (e != null) 414 { 415 exceptions.add(e); 416 } 417 } 418 /* 419 * Check the exceptions and see if we throw them or not. 420 */ 421 for (TopologyCacheException e : exceptions) 422 { 423 switch (e.getType()) 424 { 425 case NOT_GLOBAL_ADMINISTRATOR: 426 exceptionMsgs.add(INFO_NOT_GLOBAL_ADMINISTRATOR_PROVIDED.get()); 427 428 break; 429 case GENERIC_CREATING_CONNECTION: 430 if (isCertificateException(e.getCause())) 431 { 432 exceptionMsgs.add( 433 INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER.get( 434 e.getHostPort(), e.getCause().getMessage())); 435 } 436 else 437 { 438 exceptionMsgs.add(Utils.getMessage(e)); 439 } 440 break; 441 default: 442 exceptionMsgs.add(Utils.getMessage(e)); 443 } 444 } 445 return exceptionMsgs; 446 } 447 448 /** 449 * Updates the monitoring information of the provided replicas using the 450 * information located in cn=monitor of a given replication server. 451 * 452 * @param replicationServer the replication server. 453 * @param candidateReplicas the collection of replicas that must be updated. 454 * @param updatedReplicas the collection of replicas that are actually 455 * updated. This list is updated by the method. 456 */ 457 private void updateReplicas(ServerDescriptor replicationServer, 458 Collection<ReplicaDescriptor> candidateReplicas, 459 Collection<ReplicaDescriptor> updatedReplicas) 460 throws NamingException 461 { 462 SearchControls ctls = new SearchControls(); 463 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); 464 ctls.setReturningAttributes( 465 new String[] 466 { 467 "approx-older-change-not-synchronized-millis", "missing-changes", 468 "domain-name", "server-id" 469 }); 470 471 InitialLdapContext ctx = null; 472 NamingEnumeration<SearchResult> monitorEntries = null; 473 try 474 { 475 ServerLoader loader = 476 getServerLoader(replicationServer.getAdsProperties()); 477 ctx = loader.createContext(); 478 479 monitorEntries = ctx.search( 480 new LdapName("cn=monitor"), "(missing-changes=*)", ctls); 481 482 while (monitorEntries.hasMore()) 483 { 484 SearchResult sr = monitorEntries.next(); 485 486 String dn = ConnectionUtils.getFirstValue(sr, "domain-name"); 487 int replicaId = -1; 488 try 489 { 490 String sid = ConnectionUtils.getFirstValue(sr, "server-id"); 491 if (sid == null) 492 { 493 // This is not a replica, but a replication server. Skip it 494 continue; 495 } 496 replicaId = Integer.valueOf(sid); 497 } 498 catch (Throwable t) 499 { 500 logger.warn(LocalizableMessage.raw("Unexpected error reading replica ID: " + t, 501 t)); 502 } 503 504 for (ReplicaDescriptor replica : candidateReplicas) 505 { 506 if (Utils.areDnsEqual(dn, replica.getSuffix().getDN()) 507 && replica.isReplicated() 508 && replica.getReplicationId() == replicaId) 509 { 510 // This statistic is optional. 511 setAgeOfOldestMissingChange(replica, sr); 512 setMissingChanges(replica, sr); 513 updatedReplicas.add(replica); 514 } 515 } 516 } 517 } 518 catch (NameNotFoundException nse) 519 { 520 } 521 finally 522 { 523 if (monitorEntries != null) 524 { 525 try 526 { 527 monitorEntries.close(); 528 } 529 catch (Throwable t) 530 { 531 logger.warn(LocalizableMessage.raw( 532 "Unexpected error closing enumeration on monitor entries" + t, t)); 533 } 534 } 535 if (ctx != null) 536 { 537 ctx.close(); 538 } 539 } 540 } 541 542 private void setMissingChanges(ReplicaDescriptor replica, SearchResult sr) throws NamingException 543 { 544 String s = ConnectionUtils.getFirstValue(sr, "missing-changes"); 545 if (s != null) 546 { 547 try 548 { 549 replica.setMissingChanges(Integer.valueOf(s)); 550 } 551 catch (Throwable t) 552 { 553 logger.warn(LocalizableMessage.raw( 554 "Unexpected error reading missing changes: " + t, t)); 555 } 556 } 557 } 558 559 private void setAgeOfOldestMissingChange(ReplicaDescriptor replica, SearchResult sr) throws NamingException 560 { 561 String s = ConnectionUtils.getFirstValue(sr, "approx-older-change-not-synchronized-millis"); 562 if (s != null) 563 { 564 try 565 { 566 replica.setAgeOfOldestMissingChange(Long.valueOf(s)); 567 } 568 catch (Throwable t) 569 { 570 logger.warn(LocalizableMessage.raw( 571 "Unexpected error reading age of oldest change: " + t, t)); 572 } 573 } 574 } 575}