/* * 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 2014-2015 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.replication.service; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; import org.opends.server.DirectoryServerTestCase; import org.opends.server.replication.common.DSInfo; import org.opends.server.replication.common.RSInfo; import org.opends.server.replication.common.ServerState; import org.opends.server.replication.protocol.TopologyMsg; import org.opends.server.replication.service.ReplicationBroker.RSEvaluations; import org.opends.server.replication.service.ReplicationBroker.ReplicationServerInfo; import org.opends.server.replication.service.ReplicationBroker.Topology; import org.testng.Assert; import org.testng.SkipException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; import static org.opends.server.replication.service.ReplicationBroker.computeBestReplicationServer; import static org.opends.server.util.CollectionUtils.*; import static org.testng.Assert.*; @SuppressWarnings("javadoc") public class ReplicationBrokerTest extends DirectoryServerTestCase { private static enum TopologyCtorToUse { BUILD_WITH_TOPOLOGY_MSG, BUILD_WITH_DS_RS_LISTS; } private final int CURRENT_RS_ID = 91; private final int MISSING_RS_ID = 93; private final int ANOTHER_RS_ID = 94; private final DSInfo CURRENT_DS = dsInfo(11, CURRENT_RS_ID); private final DSInfo OTHER_DS = dsInfo(12, CURRENT_RS_ID); private final DSInfo MISSING_DS = dsInfo(13, CURRENT_RS_ID); private final ReplicationServerInfo CURRENT_RS = rsInfo(CURRENT_RS_ID, CURRENT_DS.getDsId(), OTHER_DS.getDsId()); private final ReplicationServerInfo MISSING_RS = rsInfo(MISSING_RS_ID, MISSING_DS.getDsId()); private final ReplicationServerInfo ANOTHER_RS = rsInfo(ANOTHER_RS_ID); @SuppressWarnings("unchecked") private DSInfo dsInfo(int dsServerId, int rsServerId) { byte z = 0; return new DSInfo(dsServerId, null, rsServerId, 0, null, false, null, z, z, EMPTY_LIST, EMPTY_LIST, EMPTY_LIST, z); } private ReplicationServerInfo rsInfo(int rsServerId, Integer... dsIds) { byte z = 0; final RSInfo info = new RSInfo(rsServerId, rsServerId + ":1389", 0, z, 0); return new ReplicationServerInfo(info, newHashSet(dsIds)); } private Map newMap(ReplicationServerInfo... infos) { if (infos.length == 0) { return Collections.emptyMap(); } final Map map = new HashMap<>(); for (ReplicationServerInfo info : infos) { map.put(info.getServerId(), info); } return map; } private void assertInvariants(final Topology topo) { assertThat(topo.replicaInfos).doesNotContainKey(CURRENT_DS.getDsId()); } private ReplicationServerInfo assertContainsRSWithDSs( Map rsInfos, ReplicationServerInfo rsInfo, Integer... connectedDSs) { return assertContainsRSWithDSs(rsInfos, rsInfo, newHashSet(connectedDSs)); } private ReplicationServerInfo assertContainsRSWithDSs( Map rsInfos, ReplicationServerInfo rsInfo, Set connectedDSs) { final ReplicationServerInfo info = find(rsInfos, rsInfo.toRSInfo()); assertNotNull(info); assertThat(info.getConnectedDSs()).containsAll(connectedDSs); return info; } private ReplicationServerInfo find(Map rsInfos, RSInfo rsInfo) { for (ReplicationServerInfo info : rsInfos.values()) { if (info.getServerId() == rsInfo.getId()) { return info; } } return null; } private Topology newTopology(TopologyCtorToUse toUse, Map replicaInfos, List rsInfos, int dsServerId, int rsServerId, Set rsUrls, Map previousRSs) { if (TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG == toUse) { final TopologyMsg topologyMsg = new TopologyMsg(replicaInfos.values(), rsInfos); return new Topology(topologyMsg, dsServerId, rsServerId, rsUrls, previousRSs); } else if (TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS == toUse) { return new Topology(replicaInfos, rsInfos, dsServerId, rsServerId, rsUrls, previousRSs); } Assert.fail("Do not know which Topology constructor to use: " + toUse); return null; } private Map newMap(DSInfo... dsInfos) { final Map results = new HashMap<>(); for (DSInfo dsInfo : dsInfos) { results.put(dsInfo.getDsId(), dsInfo); } return results; } @DataProvider public Object[][] topologyCtorProvider() { return new Object[][] { { TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG }, { TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS } }; } @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldContainNothing(TopologyCtorToUse toUse) throws Exception { final Topology topo = newTopology(toUse, EMPTY_MAP, EMPTY_LIST, CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, EMPTY_MAP); assertInvariants(topo); assertThat(topo.rsInfos).isEmpty(); } @Test @SuppressWarnings("unchecked") public void topologyShouldFilterOutCurrentDS() { final Topology topo = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS, CURRENT_DS), EMPTY_LIST, CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, EMPTY_MAP); assertInvariants(topo); assertThat(topo.rsInfos).isEmpty(); } @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldContainRSWithoutOtherDS(TopologyCtorToUse toUse) { final Topology topo = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo()), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, EMPTY_MAP); assertInvariants(topo); assertThat(topo.rsInfos).hasSize(1); assertContainsRSWithDSs(topo.rsInfos, CURRENT_RS, CURRENT_DS.getDsId()); } @Test @SuppressWarnings("unchecked") public void topologyShouldContainRSWithAllDSs_buildWithTopologyMsg() { final Topology topo = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(CURRENT_DS, OTHER_DS), newArrayList(CURRENT_RS.toRSInfo()), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, EMPTY_MAP); assertInvariants(topo); assertThat(topo.rsInfos).hasSize(1); assertContainsRSWithDSs(topo.rsInfos, CURRENT_RS, CURRENT_RS.getConnectedDSs()); } @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldStillContainRS(TopologyCtorToUse toUse) throws Exception { final Map previousRSs = newMap(CURRENT_RS); final Topology topo = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo()), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, previousRSs); assertInvariants(topo); assertThat(topo.rsInfos).hasSize(1); assertContainsRSWithDSs(topo.rsInfos, CURRENT_RS, CURRENT_RS.getConnectedDSs()); } @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldStillContainRSWithNewlyProvidedDSs(TopologyCtorToUse toUse) { final ReplicationServerInfo CURRENT_RS_WITHOUT_DS = rsInfo(CURRENT_RS_ID); final Map previousRSs = newMap(CURRENT_RS_WITHOUT_DS); final Topology topo = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo()), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, previousRSs); assertInvariants(topo); assertThat(topo.rsInfos).hasSize(1); assertContainsRSWithDSs(topo.rsInfos, CURRENT_RS, CURRENT_DS.getDsId(), OTHER_DS.getDsId()); } @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldHaveRemovedMissingRS(TopologyCtorToUse toUse) { final Map previousRSs = newMap(CURRENT_RS, MISSING_RS); final Topology topo = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo()), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), EMPTY_SET, previousRSs); assertInvariants(topo); assertThat(topo.rsInfos).hasSize(1); assertContainsRSWithDSs(topo.rsInfos, CURRENT_RS, CURRENT_RS.getConnectedDSs()); } @Test @SuppressWarnings("unchecked") public void topologyShouldHaveStampedLocallyConfiguredRSs_buildWithDsRsLists() { final Set locallyConfigured = newHashSet(CURRENT_RS.getServerURL()); final Topology topo = newTopology(TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), ANOTHER_RS.toRSInfo()), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), locallyConfigured, EMPTY_MAP); assertInvariants(topo); assertThat(topo.rsInfos).hasSize(2); ReplicationServerInfo currentRS = assertContainsRSWithDSs(topo.rsInfos, CURRENT_RS, CURRENT_RS.getConnectedDSs()); ReplicationServerInfo anotherRS = assertContainsRSWithDSs(topo.rsInfos, ANOTHER_RS); assertThat(currentRS.isLocallyConfigured()).isTrue(); assertThat(anotherRS.isLocallyConfigured()).isFalse(); } /** * A topology names a replication server before the configuration of the domain lists it: the next * topology, built once the configuration lists it, must stamp it as locally configured. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldStampRSAddedToTheConfigurationAfterThePreviousTopology(TopologyCtorToUse toUse) { final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "127.0.0.1:1389", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL()), EMPTY_MAP); assertThat(find(before.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); final RSInfo configuredRS = new RSInfo(ANOTHER_RS_ID, "localhost:1389", 0, (byte) 0, 0); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), configuredRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "localhost:1389"), before.rsInfos); assertInvariants(after); final ReplicationServerInfo anotherRS = find(after.rsInfos, configuredRS); assertThat(anotherRS.isLocallyConfigured()).isTrue(); assertThat(anotherRS.getServerURL()).isEqualTo("localhost:1389"); assertThat(find(after.rsInfos, CURRENT_RS.toRSInfo()).isLocallyConfigured()).isTrue(); } /** * A configured replication server announces in the topology messages a URL other than the * configured one (a port mapped by NAT or by Docker), and the broker hears of it first that way. * Once a phase-one handshake has reached it at the configured URL, it is locally configured, and * stays so when the next topology message names it with the announced URL again. */ @Test @SuppressWarnings("unchecked") public void topologyShouldStampRSReachedAtAConfiguredURLItDoesNotAnnounce() { final Set configured = newHashSet(CURRENT_RS.getServerURL(), "localhost:28989"); final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:8989", 0, (byte) 0, 0); final Topology heard = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), configured, EMPTY_MAP); assertThat(find(heard.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); final RSInfo reachedRS = new RSInfo(ANOTHER_RS_ID, "localhost:28989", 0, (byte) 0, 0); final Topology connected = newTopology(TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), reachedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, configured, heard.rsInfos); ReplicationServerInfo anotherRS = find(connected.rsInfos, reachedRS); assertThat(anotherRS.isLocallyConfigured()).isTrue(); assertThat(anotherRS.getServerURL()).isEqualTo("localhost:28989"); final Topology next = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, configured, connected.rsInfos); anotherRS = find(next.rsInfos, announcedRS); assertThat(anotherRS.isLocallyConfigured()).isTrue(); assertThat(anotherRS.getServerURL()).isEqualTo("localhost:28989"); } /** * A replication server the previous topology stamped as locally configured stays so as long as the * configuration lists the URL it is known with, even when the topology names it with another URL (a port * mapped by NAT or by Docker) and its host name cannot be resolved at that moment: losing the flag * would make the broker leave the server it is connected to. *

* The case shows that the host is not resolved only where resolving it fails: a resolver which * answers for any name (the fake addresses of a proxy or VPN DNS) skips it. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldKeepStampedAConfiguredRSWithoutResolvingItsHost(TopologyCtorToUse toUse) { try { final InetAddress[] answer = InetAddress.getAllByName("rs.invalid"); throw new SkipException("The resolver answers for rs.invalid with " + Arrays.toString(answer) + ": this case could not tell a resolved host from one that is not"); } catch (UnknownHostException expected) { // The premise of this case: resolving the host fails } final String unresolvableURL = "rs.invalid:1389"; final RSInfo connectedRS = new RSInfo(ANOTHER_RS_ID, unresolvableURL, 0, (byte) 0, 0); final Map previousRSs = newMap(new ReplicationServerInfo(connectedRS, newHashSet(CURRENT_DS.getDsId()))); final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "rs.invalid:8989", 0, (byte) 0, 0); final Topology topo = newTopology(toUse, newMap(OTHER_DS), newArrayList(announcedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, newHashSet(unresolvableURL), previousRSs); assertInvariants(topo); final ReplicationServerInfo anotherRS = find(topo.rsInfos, announcedRS); assertThat(anotherRS.isLocallyConfigured()).isTrue(); assertThat(anotherRS.getServerURL()).isEqualTo(unresolvableURL); } /** * A replication server is removed from the configuration of the domain: the next topology must no * longer stamp it as locally configured. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldNotStampRSRemovedFromTheConfigurationAfterThePreviousTopology(TopologyCtorToUse toUse) { final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:1389", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "localhost:1389"), EMPTY_MAP); assertThat(find(before.rsInfos, announcedRS).isLocallyConfigured()).isTrue(); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL()), before.rsInfos); assertInvariants(after); assertThat(find(after.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); } /** * A topology names a replication server before the configuration of the domain lists it, with the URL * the configuration then lists it with: the next topology must stamp it as locally configured. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldStampRSAddedToTheConfigurationWithTheURLItAnnounces(TopologyCtorToUse toUse) { final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:1389", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL()), EMPTY_MAP); assertThat(find(before.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "localhost:1389"), before.rsInfos); assertInvariants(after); assertThat(find(after.rsInfos, announcedRS).isLocallyConfigured()).isTrue(); } /** * The broker has reconnected to a replication server a topology named before the configuration of * the domain listed it, and the topology of the new session has the infos of the phase-one handshakes: * the periodic check of the best replication server must not reject it as a server which is not * locally configured. *

* The other server is not on this host, so the same-host filter keeps only this one, and the check * returns it before it reads which server the broker is connected to: the case pins the filter of the * locally configured servers only. */ @Test @SuppressWarnings("unchecked") public void bestServerCheckShouldNotRejectTheRSAddedToTheConfiguration() { final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:1389", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL()), EMPTY_MAP); final Topology reconnected = newTopology(TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, newHashSet(CURRENT_RS.getServerURL(), "localhost:1389"), before.rsInfos); final RSEvaluations evals = computeBestReplicationServer(false, ANOTHER_RS_ID, new ServerState(), reconnected.rsInfos, CURRENT_DS.getDsId(), (byte) 0, 0); assertThat(evals.getBestRS()).isNotNull(); assertThat(evals.getBestRS().getServerId()).as(evals.toString()).isEqualTo(ANOTHER_RS_ID); } /** * The entry of the configuration which lists a replication server stamped as locally configured is * written another way, with an equivalent URL: the server must keep the flag, with the configured URL, * even when the restart of the session this asks for is refused (while a total update runs). */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldKeepStampedRSWhoseConfiguredURLIsWrittenAnotherWay(TopologyCtorToUse toUse) { final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:8989", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "localhost:8989"), EMPTY_MAP); assertThat(find(before.rsInfos, announcedRS).isLocallyConfigured()).isTrue(); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "127.0.0.1:8989"), before.rsInfos); assertInvariants(after); final ReplicationServerInfo anotherRS = find(after.rsInfos, announcedRS); assertThat(anotherRS.isLocallyConfigured()).isTrue(); assertThat(anotherRS.getServerURL()).isEqualTo("127.0.0.1:8989"); } /** * A known replication server which is not locally configured comes with a URL which is only an * equivalent of a configured one: the topology message must not resolve it, so the server gets the flag * at the next connection only. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldNotResolveTheURLAKnownRSComesWith(TopologyCtorToUse toUse) { final Set configured = newHashSet(CURRENT_RS.getServerURL(), "localhost:28989"); final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:8989", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), configured, EMPTY_MAP); assertThat(find(before.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); final RSInfo equivalentRS = new RSInfo(ANOTHER_RS_ID, "127.0.0.1:28989", 0, (byte) 0, 0); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), equivalentRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), configured, before.rsInfos); assertInvariants(after); assertThat(find(after.rsInfos, equivalentRS).isLocallyConfigured()).isFalse(); } /** * A known replication server which is not locally configured gets an equivalent of its URL in the * configuration of the domain, written another way: the next topology must not resolve it either, so * the server gets the flag at the next connection only. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldNotResolveTheURLAKnownRSIsKnownWith(TopologyCtorToUse toUse) { final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "127.0.0.1:28989", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL()), EMPTY_MAP); assertThat(find(before.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "localhost:28989"), before.rsInfos); assertInvariants(after); assertThat(find(after.rsInfos, announcedRS).isLocallyConfigured()).isFalse(); } /** * A known replication server comes with another URL in the next topology (a mapped port) while the * configuration of the domain now lists the URL it is known with: it is locally configured, without * resolving any host name. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldStampRSAddedToTheConfigurationWithTheURLItIsKnownWith(TopologyCtorToUse toUse) { final RSInfo knownRS = new RSInfo(ANOTHER_RS_ID, "127.0.0.2:28989", 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), knownRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL()), EMPTY_MAP); assertThat(find(before.rsInfos, knownRS).isLocallyConfigured()).isFalse(); final RSInfo mappedRS = new RSInfo(ANOTHER_RS_ID, "127.0.0.3:28989", 0, (byte) 0, 0); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), mappedRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), newHashSet(CURRENT_RS.getServerURL(), "127.0.0.2:28989"), before.rsInfos); assertInvariants(after); final ReplicationServerInfo rs = find(after.rsInfos, mappedRS); assertThat(rs.isLocallyConfigured()).isTrue(); assertThat(rs.getServerURL()).isEqualTo("127.0.0.2:28989"); } /** * A known replication server with no URL (protocol version below 4) is not locally configured, and the * check must not ask the configured URLs for a null one: they are a sorted set, as in the configuration, * whose contains(null) throws. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldNotStampKnownRSWithoutURL(TopologyCtorToUse toUse) { // sorted, as the configuration is: contains(null) throws final Set configured = new TreeSet<>(Arrays.asList(CURRENT_RS.getServerURL(), "localhost:8989")); final RSInfo noUrlRS = new RSInfo(ANOTHER_RS_ID, null, 0, (byte) 0, 0); final Topology before = newTopology(TopologyCtorToUse.BUILD_WITH_TOPOLOGY_MSG, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), noUrlRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), configured, EMPTY_MAP); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), noUrlRS), CURRENT_DS.getDsId(), CURRENT_RS.getServerId(), configured, before.rsInfos); assertThat(find(after.rsInfos, noUrlRS).isLocallyConfigured()).isFalse(); } /** * A locally configured replication server, known with the configured URL a phase-one handshake reached * it at, comes with another URL (a port mapped by NAT or by Docker), and the entry of the configuration * is written another way, as an equivalent of the URL it comes with: the server must keep the flag, * with the configured URL. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldKeepStampedRSWhoseConfiguredURLIsWrittenAsTheURLItComesWith(TopologyCtorToUse toUse) { final RSInfo reachedRS = new RSInfo(ANOTHER_RS_ID, "localhost:28989", 0, (byte) 0, 0); final Topology connected = newTopology(TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), reachedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, newHashSet(CURRENT_RS.getServerURL(), "localhost:28989"), EMPTY_MAP); assertThat(find(connected.rsInfos, reachedRS).isLocallyConfigured()).isTrue(); final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:8989", 0, (byte) 0, 0); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, newHashSet(CURRENT_RS.getServerURL(), "127.0.0.1:8989"), connected.rsInfos); assertInvariants(after); final ReplicationServerInfo rs = find(after.rsInfos, announcedRS); assertThat(rs.isLocallyConfigured()).isTrue(); assertThat(rs.getServerURL()).isEqualTo("127.0.0.1:8989"); } /** * A locally configured replication server, known with the configured URL a phase-one handshake reached * it at, comes with another URL, and the entry of the configuration is replaced with a URL equivalent to * neither: the server loses the flag, and keeps the URL it is known with. */ @Test(dataProvider = "topologyCtorProvider") @SuppressWarnings("unchecked") public void topologyShouldNotStampRSWhoseConfiguredURLNamesNeitherOfItsURLs(TopologyCtorToUse toUse) { final RSInfo reachedRS = new RSInfo(ANOTHER_RS_ID, "localhost:28989", 0, (byte) 0, 0); final Topology connected = newTopology(TopologyCtorToUse.BUILD_WITH_DS_RS_LISTS, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), reachedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, newHashSet(CURRENT_RS.getServerURL(), "localhost:28989"), EMPTY_MAP); assertThat(find(connected.rsInfos, reachedRS).isLocallyConfigured()).isTrue(); final RSInfo announcedRS = new RSInfo(ANOTHER_RS_ID, "localhost:8989", 0, (byte) 0, 0); final Topology after = newTopology(toUse, newMap(OTHER_DS), newArrayList(CURRENT_RS.toRSInfo(), announcedRS), CURRENT_DS.getDsId(), ANOTHER_RS_ID, newHashSet(CURRENT_RS.getServerURL(), "127.0.0.1:18989"), connected.rsInfos); assertInvariants(after); final ReplicationServerInfo rs = find(after.rsInfos, announcedRS); assertThat(rs.isLocallyConfigured()).isFalse(); assertThat(rs.getServerURL()).isEqualTo("localhost:28989"); } }