| | |
| | | * |
| | | * Copyright 2007-2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2013-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.admin.ads; |
| | | |
| | |
| | | private int nbEntries = -1; |
| | | private ServerDescriptor server; |
| | | private final Set<HostPort> replicationServers = new HashSet<>(); |
| | | /** @see InstallerHelper#getReplicationId(Set) */ |
| | | /** @see InstallerHelper#addNewReplicationId(Set) */ |
| | | private int serverId = -1; |
| | | private int missingChanges = -1; |
| | | private long ageOfOldestMissingChange = -1; |
| | |
| | | notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CONFIGURING_REPLICATION.get())); |
| | | |
| | | InstallerHelper helper = new InstallerHelper(); |
| | | Set<Integer> knownReplicaServerIds = new HashSet<>(); |
| | | Set<Integer> knownReplicationServerIds = new HashSet<>(); |
| | | final Set<Integer> knownServerIds; |
| | | if (lastLoadedCache != null) |
| | | { |
| | | for (SuffixDescriptor suffix : lastLoadedCache.getSuffixes()) |
| | | { |
| | | for (ReplicaDescriptor replica : suffix.getReplicas()) |
| | | { |
| | | knownReplicaServerIds.add(replica.getServerId()); |
| | | } |
| | | } |
| | | for (ServerDescriptor server : lastLoadedCache.getServers()) |
| | | { |
| | | Object v = server.getServerProperties().get(REPLICATION_SERVER_ID); |
| | | if (v != null) |
| | | { |
| | | knownReplicationServerIds.add((Integer) v); |
| | | } |
| | | } |
| | | knownServerIds = InstallerHelper.getUsedServerIds(lastLoadedCache.getServers()); |
| | | } |
| | | else |
| | | { |
| | | /* There is no ADS anywhere. Just use the SuffixDescriptors we found */ |
| | | Set<ServerDescriptor> servers = new HashSet<>(); |
| | | for (SuffixDescriptor suffix : getUserData().getSuffixesToReplicateOptions().getAvailableSuffixes()) |
| | | { |
| | | for (ReplicaDescriptor replica : suffix.getReplicas()) |
| | | { |
| | | knownReplicaServerIds.add(replica.getServerId()); |
| | | Object v = replica.getServer().getServerProperties().get(REPLICATION_SERVER_ID); |
| | | if (v != null) |
| | | { |
| | | knownReplicationServerIds.add((Integer) v); |
| | | } |
| | | servers.add(replica.getServer()); |
| | | } |
| | | } |
| | | knownServerIds = InstallerHelper.getUsedServerIds(servers); |
| | | } |
| | | |
| | | /* |
| | |
| | | helper.configureReplication(conn, replicationServers, |
| | | getUserData().getReplicationOptions().getReplicationPort(), |
| | | getUserData().getReplicationOptions().useSecureReplication(), |
| | | knownReplicationServerIds, |
| | | knownReplicaServerIds); |
| | | knownServerIds); |
| | | localTimeMeasureTime = System.currentTimeMillis(); |
| | | localTime = Utils.getServerClock(conn); |
| | | localServerDisplay = conn.getHostPort(); |
| | |
| | | try (ConnectionWrapper conn = getRemoteConnection(server)) |
| | | { |
| | | ConfiguredReplication repl = helper.configureReplication( |
| | | conn, remoteReplicationServers, replicationPort, enableSecureReplication, |
| | | knownReplicationServerIds, knownReplicaServerIds); |
| | | conn, remoteReplicationServers, replicationPort, enableSecureReplication, knownServerIds); |
| | | long remoteTimeMeasureTime = System.currentTimeMillis(); |
| | | long remoteTime = Utils.getServerClock(conn); |
| | | if (localTime != -1 |
| | |
| | | import org.forgerock.opendj.server.config.meta.ReplicationServerCfgDefn; |
| | | import org.forgerock.opendj.server.config.meta.ReplicationSynchronizationProviderCfgDefn; |
| | | import org.forgerock.opendj.server.config.server.BackendCfg; |
| | | import org.opends.admin.ads.ReplicaDescriptor; |
| | | import org.opends.admin.ads.ServerDescriptor; |
| | | import org.opends.admin.ads.util.ConnectionWrapper; |
| | | import org.opends.guitools.controlpanel.util.Utilities; |
| | | import org.opends.messages.BackendMessages; |
| | |
| | | * configured (it might not exist and the user specified it in the setup). |
| | | * @param useSecureReplication whether to encrypt connections with the |
| | | * replication port or not. |
| | | * @param usedReplicationServerIds the list of replication server ids that |
| | | * are already used. |
| | | * @param usedReplicaServerIds the list of server ids (domain ids) that |
| | | * are already used by replicas. |
| | | * @param usedServerIds the server ids that the replication servers and the |
| | | * replication domains of the topology already use, in one set (see |
| | | * {@link #getUsedServerIds(Collection)}). A new replication server or domain |
| | | * takes its id outside of it, and the set is updated with the ids of the |
| | | * replication server and of the domains of this server. |
| | | * @throws ApplicationException if something goes wrong. |
| | | * @return a ConfiguredReplication object describing what has been configured. |
| | | */ |
| | | public ConfiguredReplication configureReplication( |
| | | ConnectionWrapper conn, Map<DN, Set<HostPort>> replicationServers, |
| | | int replicationPort, boolean useSecureReplication, Set<Integer> usedReplicationServerIds, |
| | | Set<Integer> usedReplicaServerIds) |
| | | int replicationPort, boolean useSecureReplication, Set<Integer> usedServerIds) |
| | | throws ApplicationException |
| | | { |
| | | boolean synchProviderCreated; |
| | |
| | | { |
| | | secureReplicationEnabled = false; |
| | | } |
| | | int id = getReplicationId(usedReplicationServerIds); |
| | | usedReplicationServerIds.add(id); |
| | | int id = addNewReplicationId(usedServerIds); |
| | | replicationServer = sync.createReplicationServer( |
| | | ReplicationServerCfgDefn.getInstance(), |
| | | new ArrayList<PropertyException>()); |
| | |
| | | { |
| | | secureReplicationEnabled = false; |
| | | replicationServer = sync.getReplicationServer(); |
| | | usedReplicationServerIds.add( |
| | | replicationServer.getReplicationServerId()); |
| | | usedServerIds.add(replicationServer.getReplicationServerId()); |
| | | replicationServerCreated = false; |
| | | } |
| | | |
| | |
| | | } |
| | | if (domain == null) |
| | | { |
| | | int replicaServerId = getReplicationId(usedReplicaServerIds); |
| | | usedReplicaServerIds.add(replicaServerId); |
| | | int replicaServerId = addNewReplicationId(usedServerIds); |
| | | domainName = getDomainName(domainNames, dn); |
| | | domain = sync.createReplicationDomain( |
| | | ReplicationDomainCfgDefn.getInstance(), domainName, |
| | |
| | | } |
| | | servers = toLowerCaseStrings(entry.getValue()); |
| | | domain.setReplicationServer(servers); |
| | | usedReplicaServerIds.add(domain.getServerId()); |
| | | usedServerIds.add(domain.getServerId()); |
| | | |
| | | domain.commit(); |
| | | Set<String> addedServers = intersect(servers, oldServers); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the ID to be used for a new replication server or domain. |
| | | * @param usedIds the list of already used ids. |
| | | * Returns the server IDs that the replication servers and the replication domains of the provided |
| | | * servers use, in one set. |
| | | * <p> |
| | | * A new replication server and a new replication domain both take their ID outside of this set, so |
| | | * that no replication domain of any base DN has the ID of a replication server: a replication |
| | | * server of an earlier version takes the total update messages sent to its own ID as messages for |
| | | * itself, and the assured replication counts the acknowledgments of a replication server and of a |
| | | * directory server with the same ID as one. The set only holds what the descriptors carry: the |
| | | * domains of the backend base DNs they were loaded for, not a domain of another base DN such as one |
| | | * below a backend base DN, and nothing of a server that has no descriptor. |
| | | * |
| | | * @param servers the servers of the topology. |
| | | * @return the server IDs that the replication servers and the replication domains of the provided |
| | | * servers use. |
| | | */ |
| | | public static Set<Integer> getUsedServerIds(Collection<ServerDescriptor> servers) |
| | | { |
| | | final Set<Integer> ids = new HashSet<>(); |
| | | for (ServerDescriptor server : servers) |
| | | { |
| | | if (server.isReplicationServer()) |
| | | { |
| | | ids.add(server.getReplicationServerId()); |
| | | } |
| | | for (ReplicaDescriptor replica : server.getReplicas()) |
| | | { |
| | | if (replica.isReplicated()) |
| | | { |
| | | ids.add(replica.getServerId()); |
| | | } |
| | | } |
| | | } |
| | | return ids; |
| | | } |
| | | |
| | | /** |
| | | * Returns the ID to be used for a new replication server or domain, and adds it to the used ids, |
| | | * so that the next new replication server or domain takes another one (issue #1207). |
| | | * @param usedIds the already used ids. The new ID is taken outside of them and added to them. |
| | | * @return the ID to be used for a new replication server or domain. |
| | | */ |
| | | public static int getReplicationId(Set<Integer> usedIds) |
| | | public static int addNewReplicationId(Set<Integer> usedIds) |
| | | { |
| | | Random r = new Random(); |
| | | int id = 0; |
| | |
| | | { |
| | | id = r.nextInt(MAX_ID_VALUE); |
| | | } |
| | | usedIds.add(id); |
| | | return id; |
| | | } |
| | | |
| | |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.replication.server; |
| | | |
| | |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** |
| | | * The id of the message of the warning a peer replication server sends when the generation ids |
| | | * differ, in the form an ErrorMsg carries it on the wire since OpenDJ 3.0. |
| | | */ |
| | | private static final String GENERATION_ID_WARNING_MSG_ID = |
| | | WARN_BAD_GENERATION_ID_FROM_RS.resourceName() + "-" + WARN_BAD_GENERATION_ID_FROM_RS.ordinal(); |
| | | |
| | | /** |
| | | * The id an OpenDJ 2.6 replication server gives the same warning: the numeric id of its message |
| | | * descriptor, category SYNC (0x00E00000), severity SEVERE_WARNING (0x00020000), ordinal 77. |
| | | */ |
| | | private static final String GENERATION_ID_WARNING_MSG_ID_2X = String.valueOf(0x00E00000 | 0x00020000 | 77); |
| | | |
| | | /** |
| | | * The needed info for each received assured update message we are waiting |
| | | * acks for. |
| | | * <p> |
| | |
| | | stopMonitoringPublisher(); |
| | | } |
| | | |
| | | if (connectedRSs.containsKey(sHandler.getServerId())) |
| | | // The handler, not its server id, tells which of the two maps holds it: a directory server |
| | | // can have the server id of a replication server (issue #1207) |
| | | final boolean isDataServer = !sHandler.isReplicationServer(); |
| | | final Map<Integer, ? extends ServerHandler> connectedServers = isDataServer ? connectedDSs : connectedRSs; |
| | | if (connectedServers.containsKey(sHandler.getServerId())) |
| | | { |
| | | unregisterServerHandler(sHandler, shutdown, false); |
| | | } |
| | | else if (connectedDSs.containsKey(sHandler.getServerId())) |
| | | { |
| | | unregisterServerHandler(sHandler, shutdown, true); |
| | | unregisterServerHandler(sHandler, shutdown, isDataServer); |
| | | } |
| | | } |
| | | catch(Exception e) |
| | |
| | | /** |
| | | * Processes a message coming from one server in the topology and potentially |
| | | * forwards it to one or all other servers. |
| | | * <p> |
| | | * A directory server that has the server id of this replication server gets the messages sent to |
| | | * that id: a replication server holds no data to export or import, so the total update messages |
| | | * routed by id are for the directory server. Only the warning of a peer replication server about |
| | | * the generation id, and a message no directory server with the id can get, are for this |
| | | * replication server. |
| | | * |
| | | * @param msg |
| | | * The message received and to be processed. |
| | |
| | | */ |
| | | void process(RoutableMsg msg, ServerHandler sender) |
| | | { |
| | | if (msg.getDestination() == localReplicationServer.getServerId()) |
| | | if (isGenerationIdWarningOfAPeer(msg, sender)) |
| | | { |
| | | logger.error(ERR_ERROR_MSG_RECEIVED, ((ErrorMsg) msg).getDetails()); |
| | | return; |
| | | } |
| | | |
| | | List<ServerHandler> servers = getDestinationServers(msg, sender); |
| | | if (!servers.isEmpty()) |
| | | { |
| | | forwardMsgToAllServers(msg, servers, sender); |
| | | } |
| | | else if (msg.getDestination() == localReplicationServer.getServerId()) |
| | | { |
| | | // Handle routable messages targeted at this RS. |
| | | if (msg instanceof ErrorMsg) |
| | |
| | | } |
| | | else |
| | | { |
| | | // Forward message not destined for this RS. |
| | | List<ServerHandler> servers = getDestinationServers(msg, sender); |
| | | if (!servers.isEmpty()) |
| | | { |
| | | forwardMsgToAllServers(msg, servers, sender); |
| | | } |
| | | else |
| | | { |
| | | replyWithUnreachablePeerMsg(sender, msg); |
| | | } |
| | | replyWithUnreachablePeerMsg(sender, msg); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Returns whether the message is the warning a peer replication server sends this one when their |
| | | * generation ids differ (see {@link #receiveTopoInfoFromRS}). A directory server with the server id |
| | | * of this replication server must not get it: it would take it for the end of the total update it |
| | | * is in. The warning is told from the other messages a peer replication server sends to that id by |
| | | * the id of its message (see {@link #isGenerationIdWarningMsgID(String)}). |
| | | */ |
| | | private boolean isGenerationIdWarningOfAPeer(RoutableMsg msg, ServerHandler sender) |
| | | { |
| | | return sender.isReplicationServer() |
| | | && msg.getDestination() == localReplicationServer.getServerId() |
| | | && msg instanceof ErrorMsg |
| | | && isGenerationIdWarningMsgID(((ErrorMsg) msg).getMsgID()); |
| | | } |
| | | |
| | | /** |
| | | * Returns whether the id of the message an ErrorMsg carries is the one of the warning about the |
| | | * generation id: {@code <resource name>-<ordinal>} as replication servers send it since OpenDJ 3.0, |
| | | * or the numeric id of the message descriptor as an OpenDJ 2.6 replication server sends it. |
| | | */ |
| | | static boolean isGenerationIdWarningMsgID(String msgID) |
| | | { |
| | | return GENERATION_ID_WARNING_MSG_ID.equals(msgID) || GENERATION_ID_WARNING_MSG_ID_2X.equals(msgID); |
| | | } |
| | | |
| | | /** |
| | | * Responds to a monitor request message. |
| | | * |
| | | * @param msg |
| | |
| | | { |
| | | startMonitoringPublisher(); |
| | | |
| | | final int serverId = dsHandler.getServerId(); |
| | | if (serverId == localReplicationServer.getServerId() || connectedRSs.containsKey(serverId)) |
| | | { |
| | | warnReplicaHasTheServerIdOfAReplicationServer(serverId); |
| | | } |
| | | |
| | | // connected with new DS: store handler. |
| | | connectedDSs.put(dsHandler.getServerId(), dsHandler); |
| | | connectedDSs.put(serverId, dsHandler); |
| | | |
| | | // Tell peer RSs and DSs a new DS just connected to us |
| | | // No need to re-send TopologyMsg to this just new DS |
| | |
| | | { |
| | | startMonitoringPublisher(); |
| | | |
| | | final int serverId = rsHandler.getServerId(); |
| | | if (connectedDSs.containsKey(serverId)) |
| | | { |
| | | warnReplicaHasTheServerIdOfAReplicationServer(serverId); |
| | | } |
| | | |
| | | // connected with new RS (either outgoing or incoming |
| | | // connection): store handler. |
| | | connectedRSs.put(rsHandler.getServerId(), rsHandler); |
| | | connectedRSs.put(serverId, rsHandler); |
| | | } |
| | | |
| | | /** |
| | | * Reports a directory server connected to this replication server that has the server id of a |
| | | * replication server. This replication server routes the messages sent to that id to the |
| | | * directory server, but a replication server of an earlier version keeps those messages for itself. |
| | | */ |
| | | private void warnReplicaHasTheServerIdOfAReplicationServer(int serverId) |
| | | { |
| | | logger.warn(WARN_REPLICA_HAS_THE_SERVER_ID_OF_A_REPLICATION_SERVER, |
| | | localReplicationServer.getMonitorInstanceName(), serverId, baseDN, serverId); |
| | | } |
| | | |
| | | private void debug(String message) |
| | |
| | | private void updateConfiguration(ConnectionWrapper conn1, ConnectionWrapper conn2, EnableReplicationUserData uData) |
| | | throws ReplicationCliException |
| | | { |
| | | // Every base DN: a new replication server takes an id that no domain of any backend base DN has (issue #1207). |
| | | // The filter only decides which of the backends and domains read anyway the descriptors keep. |
| | | final TopologyCacheFilter filter = new TopologyCacheFilter(); |
| | | filter.setSearchMonitoringInformation(false); |
| | | filter.addBaseDNToSearch(ADSContext.getAdministrationSuffixDN()); |
| | | filter.addBaseDNToSearch(Constants.SCHEMA_DN); |
| | | filter.addBaseDNsToSearch(uData.getBaseDNs()); |
| | | |
| | | final ServerDescriptor serverDesc1 = createStandalone(conn1, filter); |
| | | final ServerDescriptor serverDesc2 = createStandalone(conn2, filter); |
| | |
| | | if (!argParser.isInteractive()) |
| | | { |
| | | // Inform the user of the potential errors that we found in the already registered servers. |
| | | printErrorWhenServersAlreadyHaveErrors(conn1, conn2, uData, adsCtx1, adsCtx2); |
| | | printErrorWhenServersAlreadyHaveErrors(conn1, conn2, adsCtx1, adsCtx2); |
| | | } |
| | | warnIfOnlyOneReplicationServerInTopology(uData, adsCtx1, adsCtx2); |
| | | |
| | |
| | | uData.addBaseDN(Constants.SCHEMA_DN); |
| | | } |
| | | |
| | | final Set<Integer> usedReplicationServerIds = new HashSet<>(); |
| | | final TopologyCache cache1; |
| | | final TopologyCache cache2; |
| | | try |
| | |
| | | Set<PreferredConnection> cnx = new LinkedHashSet<>(); |
| | | cnx.addAll(getPreferredConnections(conn1)); |
| | | cnx.addAll(getPreferredConnections(conn2)); |
| | | cache1 = createTopologyCacheOrNull(adsCtx1, cnx, uData); |
| | | if (cache1 != null) |
| | | { |
| | | usedReplicationServerIds.addAll(getReplicationServerIds(cache1)); |
| | | } |
| | | cache2 = createTopologyCacheOrNull(adsCtx2, cnx, uData); |
| | | if (cache1 != null) |
| | | { |
| | | usedReplicationServerIds.addAll(getReplicationServerIds(cache1)); |
| | | } |
| | | cache1 = createTopologyCacheOrNull(adsCtx1, cnx); |
| | | cache2 = createTopologyCacheOrNull(adsCtx2, cnx); |
| | | } |
| | | catch (ADSContextException adce) |
| | | { |
| | |
| | | ERROR_READING_TOPOLOGY_CACHE, tce); |
| | | } |
| | | |
| | | // The new replication servers and the new domains of every base DN take their ids outside of one set |
| | | final Set<Integer> usedServerIds = getUsedServerIds(cache1, cache2, serverDesc1, serverDesc2); |
| | | |
| | | final Set<HostPort> twoReplServers = new LinkedHashSet<>(); |
| | | addToSets(serverDesc1, uData.getServer1(), conn1, twoReplServers, usedReplicationServerIds); |
| | | addToSets(serverDesc2, uData.getServer2(), conn2, twoReplServers, usedReplicationServerIds); |
| | | addReplicationServer(serverDesc1, uData.getServer1(), conn1, twoReplServers); |
| | | addReplicationServer(serverDesc2, uData.getServer2(), conn2, twoReplServers); |
| | | |
| | | final Map<DN, Set<HostPort>> hmRepServers = new HashMap<>(); |
| | | final Map<DN, Set<Integer>> hmUsedReplicaServerIds = new HashMap<>(); |
| | | for (DN baseDN : uData.getBaseDNs()) |
| | | { |
| | | Set<HostPort> repServersForBaseDN = new LinkedHashSet<>(); |
| | |
| | | repServersForBaseDN.addAll(getReplicationServers(baseDN, cache2, serverDesc2)); |
| | | repServersForBaseDN.addAll(twoReplServers); |
| | | hmRepServers.put(baseDN, repServersForBaseDN); |
| | | |
| | | Set<Integer> replicaServerIds = new HashSet<>(); |
| | | addReplicaServerIds(replicaServerIds, serverDesc1, baseDN); |
| | | addReplicaServerIds(replicaServerIds, serverDesc2, baseDN); |
| | | if (cache1 != null) |
| | | { |
| | | for (ServerDescriptor server : cache1.getServers()) |
| | | { |
| | | addReplicaServerIds(replicaServerIds, server, baseDN); |
| | | } |
| | | } |
| | | if (cache2 != null) |
| | | { |
| | | for (ServerDescriptor server : cache2.getServers()) |
| | | { |
| | | addReplicaServerIds(replicaServerIds, server, baseDN); |
| | | } |
| | | } |
| | | hmUsedReplicaServerIds.put(baseDN, replicaServerIds); |
| | | } |
| | | |
| | | final Set<HostPort> allRepServers = new LinkedHashSet<>(); |
| | |
| | | |
| | | Set<String> alreadyConfiguredReplicationServers = new HashSet<>(); |
| | | configureServer(conn1, serverDesc1, uData.getServer1(), argParser.server1.replicationPortArg, |
| | | usedReplicationServerIds, allRepServers, alreadyConfiguredReplicationServers, |
| | | usedServerIds, allRepServers, alreadyConfiguredReplicationServers, |
| | | WARN_FIRST_REPLICATION_SERVER_ALREADY_CONFIGURED); |
| | | configureServer(conn2, serverDesc2, uData.getServer2(), argParser.server2.replicationPortArg, |
| | | usedReplicationServerIds, allRepServers, alreadyConfiguredReplicationServers, |
| | | usedServerIds, allRepServers, alreadyConfiguredReplicationServers, |
| | | WARN_SECOND_REPLICATION_SERVER_ALREADY_CONFIGURED); |
| | | |
| | | for (DN baseDN : uData.getBaseDNs()) |
| | | { |
| | | Set<HostPort> repServers = hmRepServers.get(baseDN); |
| | | Set<Integer> usedIds = hmUsedReplicaServerIds.get(baseDN); |
| | | Set<String> alreadyConfiguredServers = new HashSet<>(); |
| | | |
| | | configureToReplicateBaseDN(uData.getServer1(), conn1, serverDesc1, cache1, baseDN, |
| | | usedIds, alreadyConfiguredServers, repServers, allRepServers, alreadyConfiguredReplicationServers); |
| | | usedServerIds, alreadyConfiguredServers, repServers, allRepServers, alreadyConfiguredReplicationServers); |
| | | configureToReplicateBaseDN(uData.getServer2(), conn2, serverDesc2, cache2, baseDN, |
| | | usedIds, alreadyConfiguredServers, repServers, allRepServers, alreadyConfiguredReplicationServers); |
| | | usedServerIds, alreadyConfiguredServers, repServers, allRepServers, alreadyConfiguredReplicationServers); |
| | | } |
| | | |
| | | // Now that replication is configured in all servers, simply try to |
| | |
| | | } |
| | | |
| | | private void printErrorWhenServersAlreadyHaveErrors(ConnectionWrapper conn1, ConnectionWrapper conn2, |
| | | EnableReplicationUserData uData, final ADSContext adsCtx1, final ADSContext adsCtx2) |
| | | final ADSContext adsCtx1, final ADSContext adsCtx2) |
| | | throws ReplicationCliException |
| | | { |
| | | final Set<LocalizableMessage> messages = new LinkedHashSet<>(); |
| | |
| | | final Set<PreferredConnection> cnx = new LinkedHashSet<>(); |
| | | cnx.addAll(getPreferredConnections(conn1)); |
| | | cnx.addAll(getPreferredConnections(conn2)); |
| | | final TopologyCache cache1 = createTopologyCacheOrNull(adsCtx1, cnx, uData); |
| | | final TopologyCache cache1 = createTopologyCacheOrNull(adsCtx1, cnx); |
| | | if (cache1 != null) |
| | | { |
| | | messages.addAll(cache1.getErrorMessages()); |
| | | } |
| | | final TopologyCache cache2 = createTopologyCacheOrNull(adsCtx2, cnx, uData); |
| | | final TopologyCache cache2 = createTopologyCacheOrNull(adsCtx2, cnx); |
| | | if (cache2 != null) |
| | | { |
| | | messages.addAll(cache2.getErrorMessages()); |
| | |
| | | } |
| | | } |
| | | |
| | | private void addReplicaServerIds(Set<Integer> replicaServerIds, ServerDescriptor serverDesc1, DN baseDN) |
| | | /** |
| | | * Returns the server ids that the replication servers and the replication domains of the two |
| | | * servers and of their topologies use, in one set (issue #1207). Only the domains of a backend base |
| | | * DN are in it: the descriptors give a domain id only to the replica of the backend base DN of the |
| | | * domain, so the id of a domain of another base DN, such as one below a backend base DN, is not. |
| | | */ |
| | | static Set<Integer> getUsedServerIds(TopologyCache cache1, TopologyCache cache2, |
| | | ServerDescriptor... servers) |
| | | { |
| | | ReplicaDescriptor replica = findReplicatedReplicaForSuffixDN(serverDesc1.getReplicas(), baseDN); |
| | | if (replica != null) |
| | | List<ServerDescriptor> all = new ArrayList<>(Arrays.asList(servers)); |
| | | if (cache1 != null) |
| | | { |
| | | replicaServerIds.add(replica.getServerId()); |
| | | all.addAll(cache1.getServers()); |
| | | } |
| | | if (cache2 != null) |
| | | { |
| | | all.addAll(cache2.getServers()); |
| | | } |
| | | return InstallerHelper.getUsedServerIds(all); |
| | | } |
| | | |
| | | private void addToSets(ServerDescriptor serverDesc, EnableReplicationServerData serverData, ConnectionWrapper conn, |
| | | final Set<HostPort> twoReplServers, final Set<Integer> usedReplicationServerIds) |
| | | private void addReplicationServer(ServerDescriptor serverDesc, EnableReplicationServerData serverData, |
| | | ConnectionWrapper conn, final Set<HostPort> twoReplServers) |
| | | { |
| | | if (serverDesc.isReplicationServer()) |
| | | { |
| | | twoReplServers.add(serverDesc.getReplicationServerHostPort()); |
| | | usedReplicationServerIds.add(serverDesc.getReplicationServerId()); |
| | | } |
| | | else if (serverData.configureReplicationServer()) |
| | | { |
| | |
| | | |
| | | private void configureServer(ConnectionWrapper conn, ServerDescriptor serverDesc, |
| | | EnableReplicationServerData enableServer, IntegerArgument replicationPortArg, |
| | | Set<Integer> usedReplicationServerIds, Set<HostPort> allRepServers, |
| | | Set<Integer> usedServerIds, Set<HostPort> allRepServers, |
| | | Set<String> alreadyConfiguredReplicationServers, Arg2<Number, Number> replicationServerAlreadyConfiguredMsg) |
| | | throws ReplicationCliException |
| | | { |
| | |
| | | try |
| | | { |
| | | configureAsReplicationServer(conn, enableServer.getReplicationPort(), enableServer.isSecureReplication(), |
| | | allRepServers, usedReplicationServerIds); |
| | | allRepServers, usedServerIds); |
| | | } |
| | | catch (Exception ode) |
| | | { |
| | |
| | | ERROR_CONFIGURING_REPLICATIONSERVER, ode); |
| | | } |
| | | |
| | | private TopologyCache createTopologyCacheOrNull(ADSContext adsCtx, Set<PreferredConnection> cnx, |
| | | ReplicationUserData uData) throws ADSContextException, TopologyCacheException |
| | | private TopologyCache createTopologyCacheOrNull(ADSContext adsCtx, Set<PreferredConnection> cnx) |
| | | throws ADSContextException, TopologyCacheException |
| | | { |
| | | if (adsCtx.hasAdminData()) |
| | | { |
| | | TopologyCache cache = new TopologyCache(adsCtx, getTrustManager(sourceServerCI), getConnectTimeout()); |
| | | cache.setPreferredConnections(cnx); |
| | | // Every base DN, as for the descriptors of the two servers of enable |
| | | cache.getFilter().setSearchMonitoringInformation(false); |
| | | cache.getFilter().addBaseDNsToSearch(uData.getBaseDNs()); |
| | | cache.reloadTopology(); |
| | | return cache; |
| | | } |
| | |
| | | * the replication port or not. |
| | | * @param replicationServers the list of replication servers to which the |
| | | * replication server will communicate with. |
| | | * @param usedReplicationServerIds the set of replication server IDs that |
| | | * are already in use. The set will be updated with the replication ID |
| | | * that will be used by the newly configured replication server. |
| | | * @param usedServerIds the set of the server IDs that the replication servers |
| | | * and the replication domains already use. The set will be updated with the |
| | | * replication ID that will be used by the newly configured replication server. |
| | | * @throws OpenDsException if there is an error updating the configuration. |
| | | */ |
| | | private void configureAsReplicationServer(ConnectionWrapper conn, |
| | | int replicationPort, boolean useSecureReplication, |
| | | Set<HostPort> replicationServers, |
| | | Set<Integer> usedReplicationServerIds) throws Exception |
| | | Set<Integer> usedServerIds) throws Exception |
| | | { |
| | | Set<String> replicationServersLC = toLowerCaseStrings(replicationServers); |
| | | print(formatter.getFormattedWithPoints( |
| | |
| | | crypto.setSSLEncryption(useSecureReplication); |
| | | crypto.commit(); |
| | | } |
| | | int id = InstallerHelper.getReplicationId(usedReplicationServerIds); |
| | | usedReplicationServerIds.add(id); |
| | | int id = InstallerHelper.addNewReplicationId(usedServerIds); |
| | | rsCfgClient = sync.createReplicationServer( |
| | | ReplicationServerCfgDefn.getInstance(), |
| | | new ArrayList<PropertyException>()); |
| | |
| | | else |
| | | { |
| | | rsCfgClient = sync.getReplicationServer(); |
| | | usedReplicationServerIds.add(rsCfgClient.getReplicationServerId()); |
| | | usedServerIds.add(rsCfgClient.getReplicationServerId()); |
| | | mustCommit = addAllReplicationServers(rsCfgClient, replicationServersLC); |
| | | } |
| | | if (mustCommit) |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns a Set containing all the replication server ids found in the |
| | | * servers of a given TopologyCache object. |
| | | * @param cache the TopologyCache object to use. |
| | | * @return a Set containing all the replication server ids found in a given |
| | | * TopologyCache object. |
| | | */ |
| | | private Set<Integer> getReplicationServerIds(TopologyCache cache) |
| | | { |
| | | Set<Integer> ids = new HashSet<>(); |
| | | for (ServerDescriptor server : cache.getServers()) |
| | | { |
| | | if (server.isReplicationServer()) |
| | | { |
| | | ids.add(server.getReplicationServerId()); |
| | | } |
| | | } |
| | | return ids; |
| | | } |
| | | |
| | | /** |
| | | * Configures a replication domain for a given base DN in the server for which the connection is |
| | | * provided. |
| | | * |
| | |
| | | * the base DN of the replication domain to configure. |
| | | * @param replicationServers |
| | | * the list of replication servers to which the replication domain will communicate with. |
| | | * @param usedReplicaServerIds |
| | | * the set of replication domain IDs that are already in use. The set will be updated |
| | | * with the replication ID that will be used by the newly configured replication server. |
| | | * @param usedServerIds |
| | | * the set of the server IDs that the replication servers and the replication domains |
| | | * already use. The set will be updated with the server ID that will be used by the newly |
| | | * configured replication domain. |
| | | * @throws OpenDsException |
| | | * if there is an error updating the configuration. |
| | | */ |
| | | private void configureToReplicateBaseDN(ConnectionWrapper conn, |
| | | DN baseDN, |
| | | Set<HostPort> replicationServers, |
| | | Set<Integer> usedReplicaServerIds) throws Exception |
| | | Set<Integer> usedServerIds) throws Exception |
| | | { |
| | | Set<String> replicationServersLC = toLowerCaseStrings(replicationServers); |
| | | |
| | |
| | | boolean mustCommit = false; |
| | | if (domain == null) |
| | | { |
| | | int replicaServerId = InstallerHelper.getReplicationId(usedReplicaServerIds); |
| | | usedReplicaServerIds.add(replicaServerId); |
| | | int replicaServerId = InstallerHelper.addNewReplicationId(usedServerIds); |
| | | String domainName = InstallerHelper.getDomainName(domainNames, baseDN); |
| | | domain = sync.createReplicationDomain( |
| | | ReplicationDomainCfgDefn.getInstance(), domainName, |
| | |
| | | * provided ServerDescriptor object. |
| | | * @param baseDN the base DN to replicate. |
| | | * @param repServers the replication servers to be defined in the domain. |
| | | * @param usedIds the replication domain Ids already used. This Set is |
| | | * updated with the new domains that are used. |
| | | * @param usedIds the server Ids that the replication servers and the |
| | | * replication domains already use. This Set is updated with the new domains |
| | | * that are used. |
| | | * @param cache the TopologyCache used to retrieve the different defined |
| | | * replicas. |
| | | * @param server the ServerDescriptor that is used to identify the |
| | |
| | | return null; |
| | | } |
| | | |
| | | private ReplicaDescriptor findReplicatedReplicaForSuffixDN(Set<ReplicaDescriptor> replicas, DN baseDN) |
| | | { |
| | | for (ReplicaDescriptor replica : replicas) |
| | | { |
| | | if (replica.isReplicated() && replica.getSuffix().getDN().equals(baseDN)) |
| | | { |
| | | return replica; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Set<DN> findAllReplicatedSuffixDNs(Collection<ReplicaDescriptor> replicas) |
| | | { |
| | | Set<DN> results = new HashSet<>(); |
| | |
| | | in domain "%s" is held: a total update is being processed over that session, and stopping it \ |
| | | would end the total update. The restart runs once the total update is over, and the change is \ |
| | | sent again then; until then it is not recorded as replayed |
| | | WARN_REPLICA_HAS_THE_SERVER_ID_OF_A_REPLICATION_SERVER_332=In Replication server %s: directory \ |
| | | server DS(%d) of domain "%s" has the server ID of replication server RS(%d). A replication server \ |
| | | of an earlier version takes the total update messages for this directory server as messages for \ |
| | | itself, and the assured replication counts the acknowledgments of both servers as one: give the \ |
| | | domain a server ID that no replication server of the topology uses |
| | | ERR_MISSING_CHANGES_NOT_PUBLISHED_333=Domain "%s" could not publish the changes its replication \ |
| | | server misses: %s. It tries again within %d ms - on the session which starts meanwhile, or on one it \ |
| | | starts then - and the changes done meanwhile are left to that recovery as well |
| | |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.opendj.ldap.Entry; |
| | | import org.forgerock.opendj.ldap.LinkedAttribute; |
| | | import org.forgerock.opendj.ldap.LinkedHashMapEntry; |
| | | import org.opends.admin.ads.ADSContext; |
| | | import org.opends.admin.ads.ReplicaDescriptor; |
| | | import org.opends.admin.ads.ServerDescriptor; |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.testng.annotations.Test; |
| | | |
| | |
| | | assertThat(InstallerHelper.getTaskLogMessages(taskEntry)).isEmpty(); |
| | | assertThat(InstallerHelper.getRelevantLogMessage(InstallerHelper.getTaskLogMessages(taskEntry))).isNull(); |
| | | } |
| | | |
| | | /** |
| | | * The server ids in use are the ones of the replication servers and of the replication domains of |
| | | * every base DN, in one set: a new replication server and a new replication domain both take their |
| | | * id outside of it. A replication server takes the total update messages sent to its own id as |
| | | * messages for itself, so a domain must not get the id of a replication server, nor a replication |
| | | * server the id of a domain (issue #1207). |
| | | */ |
| | | @Test |
| | | public void usedServerIdsAreTheIdsOfTheReplicationServersAndOfTheDomainsOfEveryBaseDN() |
| | | { |
| | | final ServerDescriptor combined = server(4242, replica(1), replica(2), notReplicatedReplica()); |
| | | final ServerDescriptor directoryOnly = server(null, replica(3)); |
| | | final ServerDescriptor replicationOnly = server(4243); |
| | | |
| | | assertThat(InstallerHelper.getUsedServerIds(Arrays.asList(combined, directoryOnly, replicationOnly))) |
| | | .containsOnly(4242, 4243, 1, 2, 3); |
| | | } |
| | | |
| | | /** |
| | | * A new id is taken outside of the used ids and added to them: the replication server and the |
| | | * domains that setup or enable create next cannot take it (issue #1207). Only 4242 is free. |
| | | */ |
| | | @Test |
| | | public void newReplicationIdIsTakenOutsideOfTheUsedIdsAndAddedToThem() |
| | | { |
| | | final Set<Integer> usedIds = new HashSet<>(); |
| | | for (int id = 1; id < Short.MAX_VALUE; id++) |
| | | { |
| | | if (id != 4242) |
| | | { |
| | | usedIds.add(id); |
| | | } |
| | | } |
| | | |
| | | assertThat(InstallerHelper.addNewReplicationId(usedIds)).isEqualTo(4242); |
| | | assertThat(usedIds).contains(4242).hasSize(Short.MAX_VALUE - 1); |
| | | } |
| | | |
| | | private static ServerDescriptor server(Integer replicationServerId, ReplicaDescriptor... replicas) |
| | | { |
| | | final ServerDescriptor server = |
| | | ServerDescriptor.createStandalone(new HashMap<ADSContext.ServerProperty, Object>()); |
| | | if (replicationServerId != null) |
| | | { |
| | | server.getServerProperties().put(ServerDescriptor.ServerProperty.IS_REPLICATION_SERVER, Boolean.TRUE); |
| | | server.getServerProperties().put(ServerDescriptor.ServerProperty.REPLICATION_SERVER_ID, replicationServerId); |
| | | } |
| | | server.setReplicas(new HashSet<>(Arrays.asList(replicas))); |
| | | return server; |
| | | } |
| | | |
| | | private static ReplicaDescriptor replica(int serverId) |
| | | { |
| | | final ReplicaDescriptor replica = new ReplicaDescriptor(); |
| | | replica.setServerId(serverId); |
| | | return replica; |
| | | } |
| | | |
| | | /** A suffix of the server which is not replicated: its replica has no server id. */ |
| | | private static ReplicaDescriptor notReplicatedReplica() |
| | | { |
| | | return new ReplicaDescriptor(); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * The contents of this file are subject to the terms of the Common Development and |
| | | * Distribution License (the License). You may not use this file except in compliance with the |
| | | * License. |
| | | * |
| | | * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | * specific language governing permission and limitations under the License. |
| | | * |
| | | * When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.replication.server; |
| | | |
| | | import static org.assertj.core.api.Assertions.*; |
| | | |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Tests how a {@link ReplicationServerDomain} tells the generation id warning of a peer replication |
| | | * server by the id of its message (issue #1207). |
| | | */ |
| | | @SuppressWarnings("javadoc") |
| | | public class ReplicationServerDomainGenerationIdWarningTest extends DirectoryServerTestCase |
| | | { |
| | | @DataProvider |
| | | public Object[][] msgIDs() |
| | | { |
| | | return new Object[][] { |
| | | // since OpenDJ 3.0: <resource name>-<ordinal> |
| | | { "org.opends.messages.replication-77", true }, |
| | | // OpenDJ 2.6 (ErrorMsg, MessageDescriptor.getId() at tag 2.6.0): SYNC 0x00E00000 | SEVERE_WARNING 0x00020000 | 77 |
| | | { "14811213", true }, |
| | | // another message of the same file, in either form |
| | | { "org.opends.messages.replication-78", false }, |
| | | { "14811214", false }, |
| | | // the same ordinal with another severity (SEVERE_ERROR 0x00040000) or in another file |
| | | { "14942285", false }, |
| | | { "org.opends.messages.backend-77", false }, |
| | | { "77", false }, |
| | | { null, false }, |
| | | }; |
| | | } |
| | | |
| | | @Test(dataProvider = "msgIDs") |
| | | public void generationIdWarningIsToldByTheIdOfItsMessage(String msgID, boolean isGenerationIdWarning) |
| | | { |
| | | assertThat(ReplicationServerDomain.isGenerationIdWarningMsgID(msgID)).isEqualTo(isGenerationIdWarning); |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2008-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2025-2026 3A Systems,LLC. |
| | | * Portions Copyright 2025-2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.replication.service; |
| | | |
| | |
| | | import static org.opends.server.util.CollectionUtils.*; |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.net.SocketTimeoutException; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.SortedSet; |
| | | import java.util.concurrent.BlockingQueue; |
| | |
| | | import org.opends.server.replication.common.RSInfo; |
| | | import org.opends.server.replication.common.ServerState; |
| | | import org.opends.server.replication.common.ServerStatus; |
| | | import org.opends.server.replication.protocol.ByteArrayBuilder; |
| | | import org.opends.server.replication.protocol.ErrorMsg; |
| | | import org.opends.server.replication.protocol.ProtocolVersion; |
| | | import org.opends.server.replication.protocol.ReplicationMsg; |
| | | import org.opends.server.replication.protocol.UpdateMsg; |
| | | import org.opends.server.replication.server.ReplServerFakeConfiguration; |
| | | import org.opends.server.replication.server.ReplicationServer; |
| | | import org.opends.server.replication.server.ServerHandler; |
| | | import org.opends.server.replication.service.ReplicationDomain.ImportExportContext; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.opends.server.tasks.InitializeTask; |
| | |
| | | } |
| | | } |
| | | |
| | | @DataProvider |
| | | public Object[][] replicaWithTheIdOfItsReplicationServer() |
| | | { |
| | | // the id of the replication server is 11 |
| | | return new Object[][] { |
| | | { 1, 11 }, // the importer has it: InitializeTargetMsg and the entries are sent to 11 |
| | | { 11, 2 }, // the exporter has it: InitializeRequestMsg and the acknowledgements are sent to 11 |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * A replica whose server id is the one of the replication server it is connected to can be |
| | | * initialized, and can initialize others, through it (issue #1207). |
| | | * <p> |
| | | * The replication server takes a message for one of its replicas, not for itself: it has no data to |
| | | * export or import, and before the fix it answered every total update message sent to its own id |
| | | * with an ErrorMsg, so the total update never got through. |
| | | */ |
| | | @Test(dataProvider = "replicaWithTheIdOfItsReplicationServer") |
| | | public void exportAndImportWithAReplicaHavingTheIdOfItsReplicationServer(int exporterId, int importerId) |
| | | throws Exception |
| | | { |
| | | final int ENTRYCOUNT = 100; |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer = null; |
| | | FakeReplicationDomain exporter = null; |
| | | FakeReplicationDomain importer = null; |
| | | |
| | | try |
| | | { |
| | | int replServerPort = TestCaseUtils.findFreePort(); |
| | | replServer = createReplicationServer(11, replServerPort, "replicaWithTheIdOfItsRS", 100); |
| | | SortedSet<String> servers = newTreeSet("localhost:" + replServerPort); |
| | | |
| | | String exportedData = buildExportedData(ENTRYCOUNT); |
| | | exporter = new FakeReplicationDomain(testService, exporterId, servers, 0, exportedData, null, ENTRYCOUNT); |
| | | StringBuffer importedData = new StringBuffer(); |
| | | importer = new FakeReplicationDomain(testService, importerId, servers, 0, null, importedData, 0); |
| | | waitTopologyKnowsReplica(importer, exporterId); |
| | | waitTopologyKnowsReplica(exporter, importerId); |
| | | |
| | | importer.initializeFromRemote(exporterId, NO_INIT_TASK); |
| | | |
| | | waitEndExport(exportedData, importedData); |
| | | assertExportSucessful(exporter, importer, exportedData, importedData); |
| | | } |
| | | finally |
| | | { |
| | | disable(exporter, importer); |
| | | remove(replServer); |
| | | } |
| | | } |
| | | |
| | | @DataProvider |
| | | public Object[][] replicaWithTheIdOfAnotherReplicationServer() |
| | | { |
| | | // the replica with the id of the first replication server, 11, is the importer |
| | | return new Object[][] { |
| | | { 0, 1 }, // a replica of the first RS sends to 11: the first RS hands it on to the second |
| | | { 1, 0 }, // a replica of the second RS sends to 11: the first RS gets it from the second RS |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * A replica whose server id is the one of a replication server can be initialized across two |
| | | * replication servers, whether it is connected to that replication server or to the other one |
| | | * (issue #1207). |
| | | */ |
| | | @Test(dataProvider = "replicaWithTheIdOfAnotherReplicationServer") |
| | | public void exportAndImportAcross2ReplServersWithAReplicaHavingTheIdOfAReplicationServer( |
| | | int exporterRs, int importerRs) throws Exception |
| | | { |
| | | final int ENTRYCOUNT = 100; |
| | | final int exporterId = 1; |
| | | final int importerId = 11; |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer1 = null; |
| | | ReplicationServer replServer2 = null; |
| | | FakeReplicationDomain exporter = null; |
| | | FakeReplicationDomain importer = null; |
| | | |
| | | try |
| | | { |
| | | int[] ports = TestCaseUtils.findFreePorts(2); |
| | | replServer1 = createReplicationServer(11, ports[0], "replicaWithTheIdOfAnRS1", 100); |
| | | replServer2 = createReplicationServer(12, ports[1], "replicaWithTheIdOfAnRS2", 100, "localhost:" + ports[0]); |
| | | |
| | | String exportedData = buildExportedData(ENTRYCOUNT); |
| | | exporter = new FakeReplicationDomain(testService, exporterId, newTreeSet("localhost:" + ports[exporterRs]), |
| | | 0, exportedData, null, ENTRYCOUNT); |
| | | StringBuffer importedData = new StringBuffer(); |
| | | importer = new FakeReplicationDomain(testService, importerId, newTreeSet("localhost:" + ports[importerRs]), |
| | | 0, null, importedData, 0); |
| | | waitTopologyKnowsReplica(importer, exporterId); |
| | | waitTopologyKnowsReplica(exporter, importerId); |
| | | |
| | | importer.initializeFromRemote(exporterId, NO_INIT_TASK); |
| | | |
| | | waitEndExport(exportedData, importedData); |
| | | assertExportSucessful(exporter, importer, exportedData, importedData); |
| | | } |
| | | finally |
| | | { |
| | | disable(exporter, importer); |
| | | remove(replServer1, replServer2); |
| | | } |
| | | } |
| | | |
| | | /** A replication server reports a replica which connects to it with its own server id (issue #1207). */ |
| | | @Test |
| | | public void replicaWithTheIdOfItsReplicationServerIsReported() throws Exception |
| | | { |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer = null; |
| | | FakeReplicationDomain domain = null; |
| | | |
| | | try |
| | | { |
| | | int replServerPort = TestCaseUtils.findFreePort(); |
| | | replServer = createReplicationServer(11, replServerPort, "replicaWithTheIdOfItsRSIsReported", 100); |
| | | TestCaseUtils.ERROR_TEXT_WRITER.clear(); |
| | | |
| | | domain = new FakeReplicationDomain(testService, 11, newTreeSet("localhost:" + replServerPort), 0, 1); |
| | | |
| | | waitSharedServerIdReported(replServer, 11, testService); |
| | | } |
| | | finally |
| | | { |
| | | disable(domain); |
| | | remove(replServer); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * A replication server reports a replica which connects to it with the server id of a replication |
| | | * server it is connected to, and only that replica (issue #1207). |
| | | */ |
| | | @Test |
| | | public void replicaWithTheIdOfAConnectedReplicationServerIsReported() throws Exception |
| | | { |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer1 = null; |
| | | ReplicationServer replServer2 = null; |
| | | FakeReplicationDomain domain2 = null; |
| | | FakeReplicationDomain domain11 = null; |
| | | |
| | | try |
| | | { |
| | | int[] ports = TestCaseUtils.findFreePorts(2); |
| | | replServer1 = createReplicationServer(11, ports[0], "replicaWithTheIdOfAConnectedRS1", 100); |
| | | replServer2 = createReplicationServer(12, ports[1], "replicaWithTheIdOfAConnectedRS2", 100, |
| | | "localhost:" + ports[0]); |
| | | TestCaseUtils.ERROR_TEXT_WRITER.clear(); |
| | | // the domain of the second replication server connects it to the first one |
| | | domain2 = new FakeReplicationDomain(testService, 2, newTreeSet("localhost:" + ports[1]), 0, 1); |
| | | waitTopologyKnowsReplicationServers(domain2, 2); |
| | | |
| | | domain11 = new FakeReplicationDomain(testService, 11, newTreeSet("localhost:" + ports[1]), 0, 1); |
| | | |
| | | waitSharedServerIdReported(replServer2, 11, testService); |
| | | assertEquals(sharedServerIdRecords(replServer2, 2, testService), newArrayList(), |
| | | "a replica whose server id no replication server has must not be reported"); |
| | | } |
| | | finally |
| | | { |
| | | disable(domain2, domain11); |
| | | remove(replServer1, replServer2); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * A replication server reports a replication server which connects to it with the server id of one |
| | | * of its replicas (issue #1207). |
| | | */ |
| | | @Test |
| | | public void replicationServerWithTheIdOfAConnectedReplicaIsReported() throws Exception |
| | | { |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer1 = null; |
| | | ReplicationServer replServer2 = null; |
| | | FakeReplicationDomain domain11 = null; |
| | | |
| | | try |
| | | { |
| | | int[] ports = TestCaseUtils.findFreePorts(2); |
| | | replServer2 = createReplicationServer(12, ports[1], "rsWithTheIdOfAConnectedReplica2", 100); |
| | | domain11 = new FakeReplicationDomain(testService, 11, newTreeSet("localhost:" + ports[1]), 0, 1); |
| | | waitConnected(domain11); |
| | | TestCaseUtils.ERROR_TEXT_WRITER.clear(); |
| | | |
| | | replServer1 = createReplicationServer(11, ports[0], "rsWithTheIdOfAConnectedReplica1", 100, |
| | | "localhost:" + ports[1]); |
| | | // a replication server connects to its peers for the domains it has |
| | | replServer1.getReplicationServerDomain(testService, true); |
| | | |
| | | waitSharedServerIdReported(replServer2, 11, testService); |
| | | } |
| | | finally |
| | | { |
| | | disable(domain11); |
| | | remove(replServer1, replServer2); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * A replication server takes for itself the warning a peer replication server sends it when their |
| | | * generation ids differ, also when one of its replicas has its server id: the replica would take the |
| | | * warning for the end of the total update it is in (issue #1207). |
| | | */ |
| | | @Test |
| | | public void generationIdWarningOfAPeerIsNotForTheReplicaWithTheIdOfTheReplicationServer() throws Exception |
| | | { |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer1 = null; |
| | | ReplicationServer replServer2 = null; |
| | | ReplicationBroker broker11 = null; |
| | | ReplicationBroker broker2 = null; |
| | | |
| | | try |
| | | { |
| | | int[] ports = TestCaseUtils.findFreePorts(2); |
| | | replServer1 = createReplicationServer(11, ports[0], "genIdWarningOfAPeer1", 100); |
| | | replServer2 = createReplicationServer(12, ports[1], "genIdWarningOfAPeer2", 100); |
| | | // a replication server takes the generation id of the first replica which connects to it |
| | | broker11 = openReplicationSession(testService, 11, 100, ports[0], 1000, 1111); |
| | | broker2 = openReplicationSession(testService, 2, 100, ports[1], 1000, 2222); |
| | | TestCaseUtils.ERROR_TEXT_WRITER.clear(); |
| | | |
| | | // the second replication server connects to the first one and warns it that their generation ids differ |
| | | replServer2.applyConfigurationChange(new ReplServerFakeConfiguration( |
| | | ports[1], "genIdWarningOfAPeer2", 0, 12, 0, 100, newTreeSet("localhost:" + ports[0]))); |
| | | |
| | | waitGenerationIdWarningOfPeerReceived(testService, 11, 1111, 12, 2222); |
| | | assertNoErrorMsgReceived(broker11); |
| | | } |
| | | finally |
| | | { |
| | | stop(broker11, broker2); |
| | | remove(replServer1, replServer2); |
| | | } |
| | | } |
| | | |
| | | @DataProvider |
| | | public Object[][] errorMsgIDsOfAPeer() |
| | | { |
| | | return new Object[][] { |
| | | // the generation id warning, as replication servers send it since OpenDJ 3.0 and as OpenDJ 2.6 sends it |
| | | { "org.opends.messages.replication-77", true }, |
| | | { "14811213", true }, |
| | | // another message, in either form |
| | | { "org.opends.messages.replication-78", false }, |
| | | { "14811214", false }, |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Of the ErrorMsgs a peer replication server sends to the server id of a replication server, only the |
| | | * generation id warning stays with the replication server, in either form of the id of its message; |
| | | * the replica with that server id gets the others (issue #1207). A replication server of this version |
| | | * sends only the form used since OpenDJ 3.0, so the peer here sends ErrorMsgs decoded from their bytes. |
| | | */ |
| | | @Test(dataProvider = "errorMsgIDsOfAPeer") |
| | | public void onlyTheGenerationIdWarningOfAPeerStaysWithTheReplicationServer(String msgID, |
| | | boolean staysWithTheReplicationServer) throws Exception |
| | | { |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer1 = null; |
| | | ReplicationServer replServer2 = null; |
| | | ReplicationBroker broker11 = null; |
| | | |
| | | try |
| | | { |
| | | int[] ports = TestCaseUtils.findFreePorts(2); |
| | | replServer1 = createReplicationServer(11, ports[0], "errorMsgOfAPeer1", 100); |
| | | replServer2 = createReplicationServer(12, ports[1], "errorMsgOfAPeer2", 100, "localhost:" + ports[0]); |
| | | broker11 = openReplicationSession(testService, 11, 100, ports[0], 1000); |
| | | // a replication server connects to its peers for the domains it has |
| | | replServer2.getReplicationServerDomain(testService, true); |
| | | waitReplicationServerConnected(replServer1, testService, 12); |
| | | waitReplicationServerConnected(replServer2, testService, 11); |
| | | TestCaseUtils.ERROR_TEXT_WRITER.clear(); |
| | | |
| | | ServerHandler toReplServer1 = replServer2.getReplicationServerDomain(testService).getConnectedRSs().get(11); |
| | | String details = "error " + msgID; |
| | | toReplServer1.send(errorMsg(12, 11, msgID, details)); |
| | | // sent on the same connection after the first one: the replica gets the first one before it, or never |
| | | String lastDetails = "last error"; |
| | | toReplServer1.send(errorMsg(12, 11, "org.opends.messages.replication-78", lastDetails)); |
| | | |
| | | List<String> received = receiveErrorMsgsUntil(broker11, lastDetails); |
| | | if (staysWithTheReplicationServer) |
| | | { |
| | | assertFalse(received.contains(details), "DS(11) received the generation id warning " + msgID); |
| | | waitErrorMsgReceived(details); |
| | | } |
| | | else |
| | | { |
| | | assertTrue(received.contains(details), "DS(11) did not receive the ErrorMsg " + msgID + ": " + received); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | stop(broker11); |
| | | remove(replServer1, replServer2); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Returns an ErrorMsg that carries the provided id of message, decoded from its bytes as a replication |
| | | * server decodes it: the constructors of ErrorMsg build that id from the details. |
| | | */ |
| | | private static ErrorMsg errorMsg(int senderId, int destination, String msgID, String details) throws Exception |
| | | { |
| | | byte[] bytes = new ByteArrayBuilder() |
| | | .appendByte(14) // ReplicationMsg.MSG_TYPE_ERROR |
| | | .appendIntUTF8(senderId) |
| | | .appendIntUTF8(destination) |
| | | .appendString(msgID) |
| | | .appendString(details) |
| | | .appendLongUTF8(System.currentTimeMillis()) |
| | | .toByteArray(); |
| | | return (ErrorMsg) ReplicationMsg.generateMsg(bytes, ProtocolVersion.getCurrentVersion()); |
| | | } |
| | | |
| | | /** Returns the details of the ErrorMsgs the replica receives before the one with the provided details. */ |
| | | private List<String> receiveErrorMsgsUntil(ReplicationBroker broker, String lastDetails) throws Exception |
| | | { |
| | | final List<String> received = newArrayList(); |
| | | final long deadline = System.currentTimeMillis() + SECONDS.toMillis(30); |
| | | while (System.currentTimeMillis() < deadline) |
| | | { |
| | | final ReplicationMsg msg; |
| | | try |
| | | { |
| | | msg = broker.receive(); |
| | | } |
| | | catch (SocketTimeoutException e) |
| | | { |
| | | continue; |
| | | } |
| | | assertNotNull(msg, "DS(" + broker.getServerId() + ") lost its connection"); |
| | | if (msg instanceof ErrorMsg) |
| | | { |
| | | final String details = ((ErrorMsg) msg).getDetails().toString(); |
| | | if (details.equals(lastDetails)) |
| | | { |
| | | return received; |
| | | } |
| | | received.add(details); |
| | | } |
| | | } |
| | | throw new AssertionError("DS(" + broker.getServerId() + ") did not receive the ErrorMsg '" + lastDetails |
| | | + "', only " + received); |
| | | } |
| | | |
| | | private void waitReplicationServerConnected(ReplicationServer replServer, DN baseDN, int peerId) throws Exception |
| | | { |
| | | newTimer().repeatUntilSuccess(() -> assertTrue( |
| | | replServer.getReplicationServerDomain(baseDN, true).getConnectedRSs().containsKey(peerId), |
| | | "RS(" + peerId + ") connected to " + replServer.getMonitorInstanceName())); |
| | | } |
| | | |
| | | /** Waits for the error log record in which a replication server reports the ErrorMsg it kept. */ |
| | | private void waitErrorMsgReceived(String details) throws Exception |
| | | { |
| | | final String received = ERR_ERROR_MSG_RECEIVED.get(details).toString(); |
| | | newTimer().repeatUntilSuccess(() -> |
| | | { |
| | | final List<String> records = TestCaseUtils.ERROR_TEXT_WRITER.getMessages(); |
| | | assertTrue(records.stream().anyMatch(record -> record.contains(received)), |
| | | "no replication server reported the ErrorMsg '" + details + "': " + records); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * A replication server tells its peers that a replica has left also when the replica has the server |
| | | * id of one of these peers (issue #1207). |
| | | */ |
| | | @Test |
| | | public void replicaWithTheIdOfAPeerReplicationServerIsForgottenWhenItLeaves() throws Exception |
| | | { |
| | | DN testService = DN.valueOf("o=test"); |
| | | ReplicationServer replServer1 = null; |
| | | ReplicationServer replServer2 = null; |
| | | FakeReplicationDomain domain1 = null; |
| | | FakeReplicationDomain domain11 = null; |
| | | |
| | | try |
| | | { |
| | | int[] ports = TestCaseUtils.findFreePorts(2); |
| | | replServer1 = createReplicationServer(11, ports[0], "replicaWithTheIdOfAPeerLeaves1", 100); |
| | | replServer2 = createReplicationServer(12, ports[1], "replicaWithTheIdOfAPeerLeaves2", 100, |
| | | "localhost:" + ports[0]); |
| | | domain1 = new FakeReplicationDomain(testService, 1, newTreeSet("localhost:" + ports[0]), 0, 1); |
| | | domain11 = new FakeReplicationDomain(testService, 11, newTreeSet("localhost:" + ports[1]), 0, 1); |
| | | waitTopologyKnowsReplica(domain1, 11); |
| | | |
| | | domain11.disableService(); |
| | | |
| | | waitTopologyForgetsReplica(domain1, 11); |
| | | } |
| | | finally |
| | | { |
| | | disable(domain1, domain11); |
| | | remove(replServer1, replServer2); |
| | | } |
| | | } |
| | | |
| | | private static TestTimer newTimer() |
| | | { |
| | | return new TestTimer.Builder() |
| | | .maxSleep(30, SECONDS) |
| | | .sleepTimes(100, MILLISECONDS) |
| | | .toTimer(); |
| | | } |
| | | |
| | | private void waitConnected(ReplicationDomain domain) throws Exception |
| | | { |
| | | newTimer().repeatUntilSuccess(() -> assertTrue(domain.isConnected(), "DS(" + domain.getServerId() + ") connected")); |
| | | } |
| | | |
| | | private void waitTopologyForgetsReplica(ReplicationDomain domain, int dsId) throws Exception |
| | | { |
| | | newTimer().repeatUntilSuccess(() -> assertFalse(domain.getReplicaInfos().containsKey(dsId), |
| | | "DS(" + dsId + ") has left, and DS(" + domain.getServerId() + ") still knows it")); |
| | | } |
| | | |
| | | private void waitTopologyKnowsReplicationServers(ReplicationDomain domain, int count) throws Exception |
| | | { |
| | | newTimer().repeatUntilSuccess(() -> assertEquals(domain.getRsInfos().size(), count, |
| | | "replication servers known to DS(" + domain.getServerId() + ")")); |
| | | } |
| | | |
| | | private void waitSharedServerIdReported(ReplicationServer replServer, int serverId, DN baseDN) throws Exception |
| | | { |
| | | newTimer().repeatUntilSuccess(() -> assertFalse(sharedServerIdRecords(replServer, serverId, baseDN).isEmpty(), |
| | | replServer.getMonitorInstanceName() + " did not report that DS(" + serverId + ") has the id of an RS")); |
| | | } |
| | | |
| | | /** |
| | | * Waits for the error log record in which a replication server reports the warning a peer sent it |
| | | * because their generation ids differ. |
| | | */ |
| | | private void waitGenerationIdWarningOfPeerReceived(DN baseDN, int rsId, long rsGenerationId, int peerId, |
| | | long peerGenerationId) throws Exception |
| | | { |
| | | // the warning names the address the peer sees the replication server at, which is not known here |
| | | final String[] warning = WARN_BAD_GENERATION_ID_FROM_RS.get( |
| | | rsId, "@", rsGenerationId, baseDN, peerId, peerGenerationId).toString().split("@"); |
| | | final String received = ERR_ERROR_MSG_RECEIVED.get(warning[0]).toString(); |
| | | newTimer().repeatUntilSuccess(() -> |
| | | { |
| | | for (String record : TestCaseUtils.ERROR_TEXT_WRITER.getMessages()) |
| | | { |
| | | if (record.contains(received) && record.contains(warning[1])) |
| | | { |
| | | return; |
| | | } |
| | | } |
| | | fail("RS(" + rsId + ") did not receive the warning of RS(" + peerId + "): " |
| | | + TestCaseUtils.ERROR_TEXT_WRITER.getMessages()); |
| | | }); |
| | | } |
| | | |
| | | private void assertNoErrorMsgReceived(ReplicationBroker broker) |
| | | { |
| | | try |
| | | { |
| | | ReplicationMsg msg; |
| | | while ((msg = broker.receive()) != null) |
| | | { |
| | | assertFalse(msg instanceof ErrorMsg, "DS(" + broker.getServerId() + ") received " + msg); |
| | | } |
| | | } |
| | | catch (SocketTimeoutException expected) |
| | | { |
| | | // nothing more to receive |
| | | } |
| | | } |
| | | |
| | | /** Returns the records of the error log in which the replication server reports the provided replica. */ |
| | | private List<String> sharedServerIdRecords(ReplicationServer replServer, int serverId, DN baseDN) |
| | | { |
| | | final String message = WARN_REPLICA_HAS_THE_SERVER_ID_OF_A_REPLICATION_SERVER.get( |
| | | replServer.getMonitorInstanceName(), serverId, baseDN, serverId).toString(); |
| | | final List<String> records = newArrayList(); |
| | | for (String record : TestCaseUtils.ERROR_TEXT_WRITER.getMessages()) |
| | | { |
| | | if (record.contains(message)) |
| | | { |
| | | records.add(record); |
| | | } |
| | | } |
| | | return records; |
| | | } |
| | | |
| | | /** |
| | | * When an export requested by a remote replica cannot start (there is no |
| | | * local task reporting the failure), the requester keeps waiting for the |
| New file |
| | |
| | | /* |
| | | * The contents of this file are subject to the terms of the Common Development and |
| | | * Distribution License (the License). You may not use this file except in compliance with the |
| | | * License. |
| | | * |
| | | * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | * specific language governing permission and limitations under the License. |
| | | * |
| | | * When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.tools.dsreplication; |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | |
| | | import org.opends.admin.ads.ADSContext; |
| | | import org.opends.admin.ads.ReplicaDescriptor; |
| | | import org.opends.admin.ads.ServerDescriptor; |
| | | import org.opends.admin.ads.TopologyCache; |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.testng.annotations.Test; |
| | | |
| | | @SuppressWarnings("javadoc") |
| | | public class ReplicationCliMainTest extends DirectoryServerTestCase |
| | | { |
| | | /** |
| | | * The server ids that enable takes the ids of new replication servers and domains outside of are |
| | | * the ones of both topologies and of both servers. The replication server of the second topology |
| | | * was left out before (issue #1207): enable read the first topology twice. |
| | | */ |
| | | @Test |
| | | public void usedServerIdsAreTheIdsOfBothTopologiesAndOfBothServers() |
| | | { |
| | | final TopologyCache topology1 = topology(server(null, replica(1))); |
| | | final TopologyCache topology2 = topology(server(4242)); |
| | | |
| | | assertThat(ReplicationCliMain.getUsedServerIds(topology1, topology2, server(4243), server(null, replica(2)))) |
| | | .containsOnly(1, 4242, 4243, 2); |
| | | } |
| | | |
| | | /** A server that is not registered in any administration data has no topology. */ |
| | | @Test |
| | | public void usedServerIdsWithoutTopologiesAreTheIdsOfBothServers() |
| | | { |
| | | assertThat(ReplicationCliMain.getUsedServerIds(null, null, server(4243), server(null, replica(2)))) |
| | | .containsOnly(4243, 2); |
| | | } |
| | | |
| | | private static TopologyCache topology(ServerDescriptor... servers) |
| | | { |
| | | final TopologyCache topology = mock(TopologyCache.class); |
| | | when(topology.getServers()).thenReturn(new HashSet<>(Arrays.asList(servers))); |
| | | return topology; |
| | | } |
| | | |
| | | private static ServerDescriptor server(Integer replicationServerId, ReplicaDescriptor... replicas) |
| | | { |
| | | final ServerDescriptor server = |
| | | ServerDescriptor.createStandalone(new HashMap<ADSContext.ServerProperty, Object>()); |
| | | if (replicationServerId != null) |
| | | { |
| | | server.getServerProperties().put(ServerDescriptor.ServerProperty.IS_REPLICATION_SERVER, Boolean.TRUE); |
| | | server.getServerProperties().put(ServerDescriptor.ServerProperty.REPLICATION_SERVER_ID, replicationServerId); |
| | | } |
| | | server.setReplicas(new HashSet<>(Arrays.asList(replicas))); |
| | | return server; |
| | | } |
| | | |
| | | private static ReplicaDescriptor replica(int serverId) |
| | | { |
| | | final ReplicaDescriptor replica = new ReplicaDescriptor(); |
| | | replica.setServerId(serverId); |
| | | return replica; |
| | | } |
| | | } |