netty5/resolver-dns/src/test/java/io/netty/resolver/dns/SearchDomainTest.java

317 lines
12 KiB
Java
Raw Normal View History

/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.resolver.dns;
import io.netty.channel.EventLoopGroup;
Decouple EventLoop details from the IO handling for each transport to… (#8680) * Decouble EventLoop details from the IO handling for each transport to allow easy re-use of code and customization Motiviation: As today extending EventLoop implementations to add custom logic / metrics / instrumentations is only possible in a very limited way if at all. This is due the fact that most implementations are final or even package-private. That said even if these would be public there are the ability to do something useful with these is very limited as the IO processing and task processing are very tightly coupled. All of the mentioned things are a big pain point in netty 4.x and need improvement. Modifications: This changeset decoubled the IO processing logic from the task processing logic for the main transport (NIO, Epoll, KQueue) by introducing the concept of an IoHandler. The IoHandler itself is responsible to wait for IO readiness and process these IO events. The execution of the IoHandler itself is done by the SingleThreadEventLoop as part of its EventLoop processing. This allows to use the same EventLoopGroup (MultiThreadEventLoupGroup) for all the mentioned transports by just specify a different IoHandlerFactory during construction. Beside this core API change this changeset also allows to easily extend SingleThreadEventExecutor / SingleThreadEventLoop to add custom logic to it which then can be reused by all the transports. The ideas are very similar to what is provided by ScheduledThreadPoolExecutor (that is part of the JDK). This allows for example things like: * Adding instrumentation / metrics: * how many Channels are registered on an SingleThreadEventLoop * how many Channels were handled during the IO processing in an EventLoop run * how many task were handled during the last EventLoop / EventExecutor run * how many outstanding tasks we have ... ... * Implementing custom strategies for choosing the next EventExecutor / EventLoop to use based on these metrics. * Use different Promise / Future / ScheduledFuture implementations * decorate Runnable / Callables when submitted to the EventExecutor / EventLoop As a lot of functionalities are folded into the MultiThreadEventLoopGroup and SingleThreadEventLoopGroup this changeset also removes: * AbstractEventLoop * AbstractEventLoopGroup * EventExecutorChooser * EventExecutorChooserFactory * DefaultEventLoopGroup * DefaultEventExecutor * DefaultEventExecutorGroup Result: Fixes https://github.com/netty/netty/issues/8514 .
2019-01-23 08:32:05 +01:00
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.nio.NioHandler;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.concurrent.Future;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class SearchDomainTest {
private DnsNameResolverBuilder newResolver() {
return new DnsNameResolverBuilder(group.next())
.channelType(NioDatagramChannel.class)
.nameServerProvider(new SingletonDnsServerAddressStreamProvider(dnsServer.localAddress()))
.maxQueriesPerResolve(1)
.optResourceEnabled(false)
.ndots(1);
}
private TestDnsServer dnsServer;
private EventLoopGroup group;
private DnsNameResolver resolver;
@Before
public void before() {
Decouple EventLoop details from the IO handling for each transport to… (#8680) * Decouble EventLoop details from the IO handling for each transport to allow easy re-use of code and customization Motiviation: As today extending EventLoop implementations to add custom logic / metrics / instrumentations is only possible in a very limited way if at all. This is due the fact that most implementations are final or even package-private. That said even if these would be public there are the ability to do something useful with these is very limited as the IO processing and task processing are very tightly coupled. All of the mentioned things are a big pain point in netty 4.x and need improvement. Modifications: This changeset decoubled the IO processing logic from the task processing logic for the main transport (NIO, Epoll, KQueue) by introducing the concept of an IoHandler. The IoHandler itself is responsible to wait for IO readiness and process these IO events. The execution of the IoHandler itself is done by the SingleThreadEventLoop as part of its EventLoop processing. This allows to use the same EventLoopGroup (MultiThreadEventLoupGroup) for all the mentioned transports by just specify a different IoHandlerFactory during construction. Beside this core API change this changeset also allows to easily extend SingleThreadEventExecutor / SingleThreadEventLoop to add custom logic to it which then can be reused by all the transports. The ideas are very similar to what is provided by ScheduledThreadPoolExecutor (that is part of the JDK). This allows for example things like: * Adding instrumentation / metrics: * how many Channels are registered on an SingleThreadEventLoop * how many Channels were handled during the IO processing in an EventLoop run * how many task were handled during the last EventLoop / EventExecutor run * how many outstanding tasks we have ... ... * Implementing custom strategies for choosing the next EventExecutor / EventLoop to use based on these metrics. * Use different Promise / Future / ScheduledFuture implementations * decorate Runnable / Callables when submitted to the EventExecutor / EventLoop As a lot of functionalities are folded into the MultiThreadEventLoopGroup and SingleThreadEventLoopGroup this changeset also removes: * AbstractEventLoop * AbstractEventLoopGroup * EventExecutorChooser * EventExecutorChooserFactory * DefaultEventLoopGroup * DefaultEventExecutor * DefaultEventExecutorGroup Result: Fixes https://github.com/netty/netty/issues/8514 .
2019-01-23 08:32:05 +01:00
group = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
}
@After
public void destroy() {
if (dnsServer != null) {
dnsServer.stop();
dnsServer = null;
}
if (resolver != null) {
resolver.close();
}
group.shutdownGracefully();
}
@Test
public void testResolve() throws Exception {
Set<String> domains = new HashSet<>();
domains.add("host1.foo.com");
domains.add("host1");
domains.add("host3");
domains.add("host4.sub.foo.com");
domains.add("host5.sub.foo.com");
domains.add("host5.sub");
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(domains);
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build();
String a = "host1.foo.com";
String resolved = assertResolve(resolver, a);
assertEquals(store.getAddress("host1.foo.com"), resolved);
// host1 resolves host1.foo.com with foo.com search domain
resolved = assertResolve(resolver, "host1");
assertEquals(store.getAddress("host1.foo.com"), resolved);
// "host1." absolute query
resolved = assertResolve(resolver, "host1.");
assertEquals(store.getAddress("host1"), resolved);
// "host2" not resolved
assertNotResolve(resolver, "host2");
// "host3" does not contain a dot nor it's absolute but it should still be resolved after search list have
// been checked
resolved = assertResolve(resolver, "host3");
assertEquals(store.getAddress("host3"), resolved);
// "host3." does not contain a dot but is absolute
resolved = assertResolve(resolver, "host3.");
assertEquals(store.getAddress("host3"), resolved);
// "host4.sub" contains a dot but not resolved then resolved to "host4.sub.foo.com" with "foo.com" search domain
resolved = assertResolve(resolver, "host4.sub");
assertEquals(store.getAddress("host4.sub.foo.com"), resolved);
// "host5.sub" would have been directly resolved but since it has less than ndots the "foo.com" search domain
// is used.
resolved = assertResolve(resolver, "host5.sub");
assertEquals(store.getAddress("host5.sub.foo.com"), resolved);
}
@Test
public void testResolveAll() throws Exception {
Set<String> domains = new HashSet<>();
domains.add("host1.foo.com");
domains.add("host1");
domains.add("host3");
domains.add("host4.sub.foo.com");
domains.add("host5.sub.foo.com");
domains.add("host5.sub");
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(domains, 2);
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build();
String a = "host1.foo.com";
List<String> resolved = assertResolveAll(resolver, a);
assertEquals(store.getAddresses("host1.foo.com"), resolved);
// host1 resolves host1.foo.com with foo.com search domain
resolved = assertResolveAll(resolver, "host1");
assertEquals(store.getAddresses("host1.foo.com"), resolved);
// "host1." absolute query
resolved = assertResolveAll(resolver, "host1.");
assertEquals(store.getAddresses("host1"), resolved);
// "host2" not resolved
assertNotResolveAll(resolver, "host2");
// "host3" does not contain a dot nor it's absolute but it should still be resolved after search list have
// been checked
resolved = assertResolveAll(resolver, "host3");
assertEquals(store.getAddresses("host3"), resolved);
// "host3." does not contain a dot but is absolute
resolved = assertResolveAll(resolver, "host3.");
assertEquals(store.getAddresses("host3"), resolved);
// "host4.sub" contains a dot but not resolved then resolved to "host4.sub.foo.com" with "foo.com" search domain
resolved = assertResolveAll(resolver, "host4.sub");
assertEquals(store.getAddresses("host4.sub.foo.com"), resolved);
// "host5.sub" would have been directly resolved but since it has less than ndots the "foo.com" search domain
// is used.
resolved = assertResolveAll(resolver, "host5.sub");
assertEquals(store.getAddresses("host5.sub.foo.com"), resolved);
}
@Test
public void testMultipleSearchDomain() throws Exception {
Set<String> domains = new HashSet<>();
domains.add("host1.foo.com");
domains.add("host2.bar.com");
domains.add("host3.bar.com");
domains.add("host3.foo.com");
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(domains);
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Arrays.asList("foo.com", "bar.com")).build();
// "host1" resolves via the "foo.com" search path
String resolved = assertResolve(resolver, "host1");
assertEquals(store.getAddress("host1.foo.com"), resolved);
// "host2" resolves via the "bar.com" search path
resolved = assertResolve(resolver, "host2");
assertEquals(store.getAddress("host2.bar.com"), resolved);
// "host3" resolves via the "foo.com" search path as it is the first one
resolved = assertResolve(resolver, "host3");
assertEquals(store.getAddress("host3.foo.com"), resolved);
// "host4" does not resolve
assertNotResolve(resolver, "host4");
}
@Test
public void testSearchDomainWithNdots2() throws Exception {
Set<String> domains = new HashSet<>();
domains.add("host1.sub.foo.com");
domains.add("host2.sub.foo.com");
domains.add("host2.sub");
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(domains);
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Collections.singleton("foo.com")).ndots(2).build();
String resolved = assertResolve(resolver, "host1.sub");
assertEquals(store.getAddress("host1.sub.foo.com"), resolved);
// "host2.sub" is resolved with the foo.com search domain as ndots = 2
resolved = assertResolve(resolver, "host2.sub");
assertEquals(store.getAddress("host2.sub.foo.com"), resolved);
}
@Test
public void testSearchDomainWithNdots0() throws Exception {
Set<String> domains = new HashSet<>();
domains.add("host1");
domains.add("host1.foo.com");
domains.add("host2.foo.com");
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(domains);
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Collections.singleton("foo.com")).ndots(0).build();
// "host1" resolves directly as ndots = 0
String resolved = assertResolve(resolver, "host1");
assertEquals(store.getAddress("host1"), resolved);
// "host1.foo.com" resolves to host1.foo
resolved = assertResolve(resolver, "host1.foo.com");
assertEquals(store.getAddress("host1.foo.com"), resolved);
// "host2" shouldn't resolve because it is not in the known domain names, and "host2" has 0 dots which is not
// less ndots (which is also 0).
assertNotResolve(resolver, "host2");
}
private static void assertNotResolve(DnsNameResolver resolver, String inetHost) throws InterruptedException {
Future<InetAddress> fut = resolver.resolve(inetHost);
assertTrue(fut.await(10, TimeUnit.SECONDS));
assertFalse(fut.isSuccess());
}
private static void assertNotResolveAll(DnsNameResolver resolver, String inetHost) throws InterruptedException {
Future<List<InetAddress>> fut = resolver.resolveAll(inetHost);
assertTrue(fut.await(10, TimeUnit.SECONDS));
assertFalse(fut.isSuccess());
}
private static String assertResolve(DnsNameResolver resolver, String inetHost) throws InterruptedException {
Future<InetAddress> fut = resolver.resolve(inetHost);
assertTrue(fut.await(10, TimeUnit.SECONDS));
return fut.getNow().getHostAddress();
}
private static List<String> assertResolveAll(DnsNameResolver resolver,
String inetHost) throws InterruptedException {
Future<List<InetAddress>> fut = resolver.resolveAll(inetHost);
assertTrue(fut.await(10, TimeUnit.SECONDS));
List<String> list = new ArrayList<>();
for (InetAddress addr : fut.getNow()) {
list.add(addr.getHostAddress());
}
return list;
}
@Test
public void testExceptionMsgContainsSearchDomain() throws Exception {
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(Collections.emptySet());
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(1).build();
Future<InetAddress> fut = resolver.resolve("unknown.hostname");
assertTrue(fut.await(10, TimeUnit.SECONDS));
assertFalse(fut.isSuccess());
final Throwable cause = fut.cause();
assertThat(cause, instanceOf(UnknownHostException.class));
assertThat("search domain is included in UnknownHostException", cause.getMessage(),
containsString("foo.com"));
}
@Test
public void testExceptionMsgDoesNotContainSearchDomainIfNdotsIsNotReached() throws Exception {
TestDnsServer.MapRecordStoreA store = new TestDnsServer.MapRecordStoreA(Collections.emptySet());
dnsServer = new TestDnsServer(store);
dnsServer.start();
resolver = newResolver().searchDomains(Collections.singletonList("foo.com")).ndots(2).build();
Future<InetAddress> fut = resolver.resolve("unknown.hostname");
assertTrue(fut.await(10, TimeUnit.SECONDS));
assertFalse(fut.isSuccess());
final Throwable cause = fut.cause();
assertThat(cause, instanceOf(UnknownHostException.class));
assertThat("search domain is included in UnknownHostException", cause.getMessage(),
not(containsString("foo.com")));
}
}