Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.http.conn.DnsResolver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;

/** Unit tests for DNS-backed live-node discovery. */
public class AlternatorLiveNodesDnsDiscoveryTest {
Expand Down Expand Up @@ -76,4 +83,133 @@ public void testDnsEntrypointDiscoversDnsNodeRecords() throws Exception {
assertEquals("localhost", liveNodes.getLiveNodes().get(0).getHost());
assertEquals("node-a.internal", liveNodes.getLiveNodes().get(1).getHost());
}

/** Verifies raw IPv6 literals are bracketed for discovery, Host, and learned-node routing. */
@Test(timeout = 10000)
public void testIpv6LiteralDiscoveryAndRouting() throws Exception {
AtomicReference<String> hostHeader = new AtomicReference<>();
HttpServer ipv6Server =
startServer(
InetAddress.getByName("::1"),
"[\"::1\"]",
exchange -> hostHeader.set(exchange.getRequestHeaders().getFirst("Host")));
int ipv6Port = ipv6Server.getAddress().getPort();
SdkHttpClient httpClient = apacheClient(null);
try {
AlternatorConfig config =
AlternatorConfig.builder()
.withSeedHosts(Arrays.asList("::1"))
.withScheme("http")
.withPort(ipv6Port)
.build();
AlternatorLiveNodes liveNodes = new AlternatorLiveNodes(config, httpClient);

liveNodes.updateLiveNodes();

assertEquals("http://[::1]:" + ipv6Port, liveNodes.nextAsURI().toString());
assertEquals("[::1]:" + ipv6Port, hostHeader.get());
} finally {
httpClient.close();
ipv6Server.stop(0);
}
}

/** Verifies A-only, AAAA-only, and both cross-family DNS fallback orders. */
@Test(timeout = 10000)
public void testSingleAndDualFamilyDnsEntrypoints() throws Exception {
InetAddress ipv4 = InetAddress.getByName("127.0.0.1");
InetAddress ipv6 = InetAddress.getByName("::1");

assertDnsDiscovery(ipv4, ipv4);
assertDnsDiscovery(ipv6, ipv6);
assertDnsDiscovery(ipv4, ipv6, ipv4);
assertDnsDiscovery(ipv6, ipv4, ipv6);
}

/** Verifies an unavailable dual-stack entrypoint returns promptly and preserves its seed. */
@Test(timeout = 10000)
public void testAllDnsRecordsUnavailableKeepsSeed() throws Exception {
InetAddress ipv4 = InetAddress.getByName("127.0.0.1");
InetAddress ipv6 = InetAddress.getByName("::1");
HttpServer closedServer = startServer(ipv4, "[]", exchange -> {});
int closedPort = closedServer.getAddress().getPort();
closedServer.stop(0);
SdkHttpClient httpClient = apacheClient(host -> new InetAddress[] {ipv6, ipv4});
try {
AlternatorConfig config = dnsConfig(closedPort);
AlternatorLiveNodes liveNodes = new AlternatorLiveNodes(config, httpClient);

liveNodes.updateLiveNodes();

assertEquals(1, liveNodes.getLiveNodes().size());
assertEquals("dual.test", liveNodes.nextAsURI().getHost());
} finally {
httpClient.close();
}
}

private void assertDnsDiscovery(InetAddress listenAddress, InetAddress... resolvedAddresses)
throws Exception {
AtomicReference<String> hostHeader = new AtomicReference<>();
HttpServer dnsServer =
startServer(
listenAddress,
"[\"dual.test\"]",
exchange -> hostHeader.set(exchange.getRequestHeaders().getFirst("Host")));
int dnsPort = dnsServer.getAddress().getPort();
SdkHttpClient httpClient = apacheClient(host -> resolvedAddresses);
try {
AlternatorLiveNodes liveNodes = new AlternatorLiveNodes(dnsConfig(dnsPort), httpClient);

liveNodes.updateLiveNodes();

assertEquals(1, liveNodes.getLiveNodes().size());
assertEquals("dual.test", liveNodes.nextAsURI().getHost());
assertEquals("dual.test:" + dnsPort, hostHeader.get());
} finally {
httpClient.close();
dnsServer.stop(0);
}
}

private AlternatorConfig dnsConfig(int dnsPort) {
return AlternatorConfig.builder()
.withSeedHosts(Arrays.asList("dual.test"))
.withScheme("http")
.withPort(dnsPort)
.build();
}

private SdkHttpClient apacheClient(DnsResolver dnsResolver) {
ApacheHttpClient.Builder builder =
ApacheHttpClient.builder()
.connectionTimeout(Duration.ofMillis(500))
.socketTimeout(Duration.ofSeconds(2));
if (dnsResolver != null) {
builder.dnsResolver(dnsResolver);
}
return builder.build();
}

private HttpServer startServer(
InetAddress listenAddress, String responseBody, ExchangeObserver observer)
throws IOException {
HttpServer httpServer = HttpServer.create(new InetSocketAddress(listenAddress, 0), 0);
httpServer.createContext(
"/localnodes",
exchange -> {
observer.observe(exchange);
byte[] body = responseBody.getBytes();
exchange.sendResponseHeaders(200, body.length);
try (OutputStream output = exchange.getResponseBody()) {
output.write(body);
}
});
httpServer.start();
return httpServer;
}

private interface ExchangeObserver {
void observe(com.sun.net.httpserver.HttpExchange exchange);
}
}