DBZ-8059 Updated FakeDNS to JDK 18 InetAddressResolver SPI
This commit is contained in:
parent
b58c445ec7
commit
a21ade7ed3
@ -10,7 +10,7 @@
|
||||
import io.debezium.connector.mongodb.TestHelper;
|
||||
import io.debezium.testing.testcontainers.MongoDbDeployment;
|
||||
import io.debezium.testing.testcontainers.util.DockerUtils;
|
||||
import io.debezium.testing.testcontainers.util.FakeDns;
|
||||
import io.debezium.testing.testcontainers.util.dns.FakeDns;
|
||||
|
||||
/**
|
||||
* DocumentDb representation of {@link MongoDbDeployment}
|
||||
@ -27,7 +27,7 @@ public DocumentDb(String connectionString) {
|
||||
@Override
|
||||
public void start() {
|
||||
// DocumentDB requires SSH tunneling and custom DNS resolution
|
||||
var dns = FakeDns.getInstance().install();
|
||||
var dns = FakeDns.getInstance().enable();
|
||||
getHosts().forEach(DockerUtils::addFakeDnsEntry);
|
||||
|
||||
// Enable change streams
|
||||
|
@ -0,0 +1 @@
|
||||
io.debezium.testing.testcontainers.util.dns.FakeDnsAddressResolverProvider
|
@ -11,6 +11,8 @@
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import io.debezium.testing.testcontainers.util.dns.FakeDns;
|
||||
|
||||
public class DockerUtils {
|
||||
|
||||
public static final String CONTAINER_VM_LOG_SKIP = "container.vm.log.skip";
|
||||
@ -50,7 +52,7 @@ public static void logContainerVMBanner(Logger logger, Collection<String> hosts,
|
||||
var prop = System.getProperty(CONTAINER_VM_LOG_SKIP, "false");
|
||||
var propertySkip = Boolean.parseBoolean(prop);
|
||||
|
||||
if (propertySkip || skip || !isContainerVM() || FakeDns.getInstance().isInstalled()) {
|
||||
if (propertySkip || skip || !isContainerVM() || FakeDns.getInstance().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -78,7 +80,7 @@ public static void logContainerVMBanner(Logger logger, Collection<String> hosts,
|
||||
*/
|
||||
public static void enableFakeDnsIfRequired() {
|
||||
if (isContainerVM()) {
|
||||
FakeDns.getInstance().install();
|
||||
FakeDns.getInstance().enable();
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +88,7 @@ public static void enableFakeDnsIfRequired() {
|
||||
* Disables {@link FakeDns} if enabled
|
||||
*/
|
||||
public static void disableFakeDns() {
|
||||
FakeDns.getInstance().restore();
|
||||
FakeDns.getInstance().disable();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,7 +107,7 @@ public static void addFakeDnsEntry(String hostname) {
|
||||
* @param address resolution address
|
||||
*/
|
||||
public static void addFakeDnsEntry(String hostname, InetAddress address) {
|
||||
if (FakeDns.getInstance().isInstalled()) {
|
||||
if (FakeDns.getInstance().isEnabled()) {
|
||||
FakeDns.getInstance().addResolution(hostname, address);
|
||||
}
|
||||
}
|
||||
@ -126,7 +128,7 @@ public static void removeFakeDnsEntry(String hostname) {
|
||||
* @param address resolution address
|
||||
*/
|
||||
public static void removeFakeDnsEntry(String hostname, InetAddress address) {
|
||||
if (FakeDns.getInstance().isInstalled()) {
|
||||
if (FakeDns.getInstance().isEnabled()) {
|
||||
FakeDns.getInstance().removeResolution(hostname, address);
|
||||
}
|
||||
}
|
||||
|
@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright Debezium Authors.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.testing.testcontainers.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Fake DNS resolver which allows tests to work well with testcontainer under Docker Desktop
|
||||
*
|
||||
* Adaptation of https://github.com/CorfuDB/CorfuDB/blob/master/it/src/main/java/org/corfudb/universe/universe/docker/FakeDns.java
|
||||
*/
|
||||
public class FakeDns {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FakeDns.class);
|
||||
|
||||
private static final FakeDns instance = new FakeDns();
|
||||
|
||||
private final Map<String, InetAddress> forwardResolutions = new HashMap<>();
|
||||
|
||||
private Object originalNameService = null;
|
||||
|
||||
/**
|
||||
* whether the fake resolver has been installed
|
||||
*/
|
||||
private boolean installed = false;
|
||||
|
||||
private FakeDns() {
|
||||
}
|
||||
|
||||
public static FakeDns getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void addResolution(String hostname, InetAddress ip) {
|
||||
synchronized (this) {
|
||||
forwardResolutions.put(hostname, ip);
|
||||
}
|
||||
LOGGER.info("Added dns resolution: {} -> {}", hostname, ip);
|
||||
}
|
||||
|
||||
public void removeResolution(String hostname, InetAddress ip) {
|
||||
synchronized (this) {
|
||||
forwardResolutions.remove(hostname, ip);
|
||||
}
|
||||
LOGGER.info("Removed dns resolution: {} -> {}", hostname, ip);
|
||||
}
|
||||
|
||||
public synchronized boolean isInstalled() {
|
||||
return installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the fake DNS resolver into the Java runtime.
|
||||
*/
|
||||
public FakeDns install() {
|
||||
synchronized (this) {
|
||||
if (installed) {
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
originalNameService = installDns();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
installed = true;
|
||||
}
|
||||
LOGGER.info("FakeDns is now ENABLED in JVM Runtime");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore default DNS resolver in Java runtime.
|
||||
**/
|
||||
public FakeDns restore() {
|
||||
synchronized (this) {
|
||||
if (!installed) {
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
restoreDns(originalNameService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
installed = false;
|
||||
}
|
||||
LOGGER.info("FakeDns is now DISABLED in JVM Runtime");
|
||||
return this;
|
||||
}
|
||||
|
||||
private Object installDns()
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchFieldException {
|
||||
// Override the NameService in Java 9 or later.
|
||||
final Class<?> nameServiceInterface = Class.forName("java.net.InetAddress$NameService");
|
||||
Field field = InetAddress.class.getDeclaredField("nameService");
|
||||
|
||||
// Get the default NameService to fall back to.
|
||||
Method method = InetAddress.class.getDeclaredMethod("createNameService");
|
||||
method.setAccessible(true);
|
||||
Object fallbackNameService = method.invoke(null);
|
||||
|
||||
// Install a wrapping NameService proxy instance
|
||||
Object installedNameService = Proxy.newProxyInstance(
|
||||
nameServiceInterface.getClassLoader(),
|
||||
new Class<?>[]{ nameServiceInterface },
|
||||
new NameServiceListener(fallbackNameService));
|
||||
|
||||
// Set the NameService on the InetAddress field
|
||||
field.setAccessible(true);
|
||||
field.set(InetAddress.class, installedNameService);
|
||||
|
||||
return fallbackNameService;
|
||||
}
|
||||
|
||||
private void restoreDns(Object nameService)
|
||||
throws IllegalAccessException, NoSuchFieldException {
|
||||
Field field = InetAddress.class.getDeclaredField("nameService");
|
||||
field.setAccessible(true);
|
||||
field.set(InetAddress.class, nameService);
|
||||
}
|
||||
|
||||
public static <T extends Throwable> void propagateIfPossible(Throwable throwable, Class<T> declaredType) throws T {
|
||||
if (declaredType.isInstance(throwable)) {
|
||||
throw declaredType.cast(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NameServiceListener with a NameService implementation to fallback to.
|
||||
*/
|
||||
private class NameServiceListener implements InvocationHandler {
|
||||
|
||||
private final Object fallbackNameService;
|
||||
|
||||
/**
|
||||
* Creates {@link NameServiceListener} with fallback NameService
|
||||
* The parameter is untyped as {@code java.net.InetAddress$NameService} is private
|
||||
*
|
||||
* @param fallbackNameService fallback NameService
|
||||
*/
|
||||
NameServiceListener(Object fallbackNameService) {
|
||||
this.fallbackNameService = fallbackNameService;
|
||||
}
|
||||
|
||||
private InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException {
|
||||
InetAddress inetAddress;
|
||||
|
||||
synchronized (FakeDns.this) {
|
||||
inetAddress = forwardResolutions.get(host);
|
||||
}
|
||||
if (inetAddress != null) {
|
||||
return new InetAddress[]{ inetAddress };
|
||||
}
|
||||
|
||||
return invokeFallBackMethod("lookupAllHostAddr", InetAddress[].class, host);
|
||||
}
|
||||
|
||||
private String getHostByAddr(byte[] addr) throws UnknownHostException {
|
||||
return invokeFallBackMethod("getHostsByAddr", String.class, addr);
|
||||
}
|
||||
|
||||
private <T, P> T invokeFallBackMethod(String methodName, Class<T> returnType, P param) throws UnknownHostException {
|
||||
try {
|
||||
Method method = fallbackNameService
|
||||
.getClass()
|
||||
.getDeclaredMethod(methodName, param.getClass());
|
||||
|
||||
method.setAccessible(true);
|
||||
var result = method.invoke(fallbackNameService, param);
|
||||
return returnType.cast(result);
|
||||
}
|
||||
catch (ReflectiveOperationException | SecurityException e) {
|
||||
propagateIfPossible(e.getCause(), UnknownHostException.class);
|
||||
throw new AssertionError("unexpected reflection issue", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
switch (method.getName()) {
|
||||
case "lookupAllHostAddr":
|
||||
return lookupAllHostAddr((String) args[0]);
|
||||
case "getHostByAddr":
|
||||
return getHostByAddr((byte[]) args[0]);
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright Debezium Authors.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.testing.testcontainers.util.dns;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Fake DNS resolver which allows tests to work well with testcontainer under Docker Desktop
|
||||
*
|
||||
* Adaptation of https://github.com/CorfuDB/CorfuDB/blob/master/it/src/main/java/org/corfudb/universe/universe/docker/FakeDns.java
|
||||
*/
|
||||
public class FakeDns {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(FakeDns.class);
|
||||
|
||||
private static final FakeDns instance = new FakeDns();
|
||||
|
||||
private final Map<String, InetAddress> registry;
|
||||
private final AtomicBoolean enabled;
|
||||
|
||||
private FakeDns() {
|
||||
this.enabled = new AtomicBoolean(false);
|
||||
this.registry = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public static FakeDns getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void addResolution(String hostname, InetAddress ip) {
|
||||
registry.put(hostname, ip);
|
||||
LOGGER.info("Added dns resolution: {} -> {}", hostname, ip);
|
||||
}
|
||||
|
||||
public void removeResolution(String hostname, InetAddress ip) {
|
||||
registry.remove(hostname, ip);
|
||||
LOGGER.info("Removed dns resolution: {} -> {}", hostname, ip);
|
||||
}
|
||||
|
||||
public Optional<InetAddress> resolve(String hostname) {
|
||||
return isEnabled()
|
||||
? resolveInternal(hostname)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<String> resolve(byte[] address) {
|
||||
return isEnabled()
|
||||
? resolveInternal(address)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<InetAddress> resolveInternal(String hostname) {
|
||||
LOGGER.info("Performing Forward Lookup for HOST : {}", hostname);
|
||||
return Optional.of(registry.get(hostname));
|
||||
}
|
||||
|
||||
private Optional<String> resolveInternal(byte[] address) {
|
||||
LOGGER.info("Performing Reverse Lookup for Address : {}", Arrays.toString(address));
|
||||
return registry.keySet()
|
||||
.stream()
|
||||
.filter(host -> Arrays.equals(registry.get(host).getAddress(), address))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the fake DNS registry
|
||||
*/
|
||||
public FakeDns enable() {
|
||||
this.enabled.set(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the fake DNS registry
|
||||
**/
|
||||
public FakeDns disable() {
|
||||
this.enabled.set(false);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright Debezium Authors.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.testing.testcontainers.util.dns;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.spi.InetAddressResolver;
|
||||
import java.net.spi.InetAddressResolverProvider;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Custom {@link InetAddressResolverProvider} that uses {@link FakeDnsAddressResolver} to resolve addresses.
|
||||
|
||||
*/
|
||||
public class FakeDnsAddressResolverProvider extends InetAddressResolverProvider {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FakeDnsAddressResolverProvider.class);
|
||||
|
||||
@Override
|
||||
public InetAddressResolver get(Configuration configuration) {
|
||||
LOGGER.info("Using Custom Address Resolver : {} ", this.name());
|
||||
LOGGER.info("Registry initialised");
|
||||
return new FakeDnsAddressResolver(FakeDns.getInstance(), configuration.builtinResolver());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return FakeDnsAddressResolver.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* When {@link FakeDns} is enabled the resolver will first perform lookups in the fake DNS registry.
|
||||
* If the address is not found, the resolver will delegate to the fallback resolver.
|
||||
* If {@link FakeDns} is disabled, the resolver will only delegate to the fallback resolver.
|
||||
*/
|
||||
public static final class FakeDnsAddressResolver implements InetAddressResolver {
|
||||
private final InetAddressResolver fallbackResolver;
|
||||
private final FakeDns fakeDns;
|
||||
|
||||
public FakeDnsAddressResolver(FakeDns fakeDns, InetAddressResolver fallbackResolver) {
|
||||
this.fakeDns = fakeDns;
|
||||
this.fallbackResolver = fallbackResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
|
||||
var maybeAddress = fakeDns.resolve(host);
|
||||
|
||||
return maybeAddress.isPresent()
|
||||
? maybeAddress.stream()
|
||||
: fallbackResolver.lookupByName(host, lookupPolicy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lookupByAddress(byte[] address) throws UnknownHostException {
|
||||
var maybeHost = fakeDns.resolve(address);
|
||||
|
||||
return maybeHost.isPresent()
|
||||
? maybeHost.get()
|
||||
: fallbackResolver.lookupByAddress(address);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
io.debezium.testing.testcontainers.util.dns.FakeDnsAddressResolverProvider
|
@ -10,6 +10,13 @@
|
||||
<artifactId>debezium-testing</artifactId>
|
||||
<name>Debezium Testing</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${debezium.java.source}</maven.compiler.source>
|
||||
<maven.compiler.target>${debezium.java.specific.target}</maven.compiler.target>
|
||||
<maven.compiler.release>${debezium.java.specific.target}</maven.compiler.release>
|
||||
<maven.compiler.testRelease>${debezium.java.specific.target}</maven.compiler.testRelease>
|
||||
</properties>
|
||||
<modules>
|
||||
<module>debezium-testing-testcontainers</module>
|
||||
<module>debezium-testing-system</module>
|
||||
|
Loading…
Reference in New Issue
Block a user