google-nomulus/javatests/google/registry/proxy/ProtocolModuleTest.java
jianglai 6ca523386a Add QuotaHandler to GCP proxy
The quota handler terminates connections when quota is exceeded.

The next CL will add instrumentation for quota related metrics.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=185042675
2018-02-20 15:36:23 -05:00

290 lines
10 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed 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 google.registry.proxy;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.proxy.ProxyConfig.Environment.TEST;
import static google.registry.proxy.ProxyConfig.getProxyConfig;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.MoreExecutors;
import dagger.Component;
import dagger.Module;
import dagger.Provides;
import google.registry.proxy.EppProtocolModule.EppProtocol;
import google.registry.proxy.HealthCheckProtocolModule.HealthCheckProtocol;
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
import google.registry.proxy.WhoisProtocolModule.WhoisProtocol;
import google.registry.proxy.handler.BackendMetricsHandler;
import google.registry.proxy.handler.ProxyProtocolHandler;
import google.registry.proxy.handler.QuotaHandler.EppQuotaHandler;
import google.registry.proxy.handler.QuotaHandler.WhoisQuotaHandler;
import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler;
import google.registry.proxy.handler.RelayHandler.FullHttpResponseRelayHandler;
import google.registry.proxy.handler.SslClientInitializer;
import google.registry.proxy.handler.SslServerInitializer;
import google.registry.testing.FakeClock;
import google.registry.util.Clock;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.junit.Before;
/**
* Base class for end-to-end tests of a {@link Protocol}.
*
* <p>The end-to-end tests ensures that the business logic that a {@link Protocol} defines are
* correctly performed by various handlers attached to its pipeline. Non-business essential handlers
* should be excluded.
*
* <p>Subclass should implement an no-arg constructor that calls constructors of this class,
* providing the method reference of the {@link TestComponent} method to call to obtain the list of
* {@link ChannelHandler} providers for the {@link Protocol} to test, and optionally a set of {@link
* ChannelHandler} classes to exclude from testing.
*/
public abstract class ProtocolModuleTest {
protected static final ProxyConfig PROXY_CONFIG = getProxyConfig(TEST);
protected TestComponent testComponent;
/**
* Default list of handler classes that are not of interest in end-to-end testing of the {@link
* Protocol}.
*/
private static final ImmutableSet<Class<? extends ChannelHandler>> DEFAULT_EXCLUDED_HANDLERS =
ImmutableSet.of(
// The PROXY protocol is only used when the proxy is behind the GCP load balancer. It is
// not part of any business logic.
ProxyProtocolHandler.class,
// SSL is part of the business logic for some protocol (EPP for example), but its
// impact is isolated. Including it makes tests much more complicated. It should be tested
// separately in its own unit tests.
SslClientInitializer.class,
SslServerInitializer.class,
// These two handlers provide essential functionalities for the proxy to operate, but they
// do not directly implement the business logic of a well-defined protocol. They should be
// tested separately in their respective unit tests.
FullHttpRequestRelayHandler.class,
FullHttpResponseRelayHandler.class,
// The rest are not part of business logic and do not need to be tested, obviously.
LoggingHandler.class,
// Metrics instrumentation is tested separately.
BackendMetricsHandler.class,
// Quota management is tested separately.
WhoisQuotaHandler.class,
EppQuotaHandler.class,
ReadTimeoutHandler.class);
protected EmbeddedChannel channel;
/**
* Method reference to the component method that exposes the list of handler providers for the
* specific {@link Protocol} in interest.
*/
protected final Function<TestComponent, ImmutableList<Provider<? extends ChannelHandler>>>
handlerProvidersMethod;
protected final ImmutableSet<Class<? extends ChannelHandler>> excludedHandlers;
protected ProtocolModuleTest(
Function<TestComponent, ImmutableList<Provider<? extends ChannelHandler>>>
handlerProvidersMethod,
ImmutableSet<Class<? extends ChannelHandler>> excludedHandlers) {
this.handlerProvidersMethod = handlerProvidersMethod;
this.excludedHandlers = excludedHandlers;
}
protected ProtocolModuleTest(
Function<TestComponent, ImmutableList<Provider<? extends ChannelHandler>>>
handlerProvidersMethod) {
this(handlerProvidersMethod, DEFAULT_EXCLUDED_HANDLERS);
}
/** Excludes handler providers that are not of interested for testing. */
private ImmutableList<Provider<? extends ChannelHandler>> excludeHandlerProvidersForTesting(
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
return handlerProviders
.stream()
.filter(handlerProvider -> !excludedHandlers.contains(handlerProvider.get().getClass()))
.collect(toImmutableList());
}
protected void initializeChannel(Consumer<Channel> initializer) {
channel =
new EmbeddedChannel(
new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
initializer.accept(ch);
}
});
}
/** Adds handlers to the channel pipeline, excluding any one in {@link #excludedHandlers}. */
void addAllTestableHandlers(Channel ch) {
for (Provider<? extends ChannelHandler> handlerProvider :
excludeHandlerProvidersForTesting(handlerProvidersMethod.apply(testComponent))) {
ch.pipeline().addLast(handlerProvider.get());
}
}
static TestComponent makeTestComponent(FakeClock fakeClock) {
return DaggerProtocolModuleTest_TestComponent.builder()
.testModule(new TestModule(new FakeClock()))
.build();
}
@Before
public void setUp() throws Exception {
testComponent = makeTestComponent(new FakeClock());
initializeChannel(this::addAllTestableHandlers);
}
/**
* Component used to obtain the list of {@link ChannelHandler} providers for each {@link
* Protocol}.
*/
@Singleton
@Component(
modules = {
TestModule.class,
WhoisProtocolModule.class,
EppProtocolModule.class,
HealthCheckProtocolModule.class,
HttpsRelayProtocolModule.class
}
)
interface TestComponent {
@WhoisProtocol
ImmutableList<Provider<? extends ChannelHandler>> whoisHandlers();
@EppProtocol
ImmutableList<Provider<? extends ChannelHandler>> eppHandlers();
@HealthCheckProtocol
ImmutableList<Provider<? extends ChannelHandler>> healthCheckHandlers();
@HttpsRelayProtocol
ImmutableList<Provider<? extends ChannelHandler>> httpsRelayHandlers();
}
/**
* Module that provides bindings used in tests.
*
* <p>Most of the binding provided in this module should be either a fake, or a {@link
* ChannelHandler} that is excluded, and annotated with {@code @Singleton}. This module acts as a
* replacement for {@link ProxyModule} used in production component. Providing a handler that is
* part of the business logic of a {@link Protocol} from this module is a sign that the binding
* should be provided in the respective {@code ProtocolModule} instead.
*/
@Module
static class TestModule {
/**
* A fake clock that is explicitly provided. Users can construct a module with a controller
* clock.
*/
private final FakeClock fakeClock;
TestModule(FakeClock fakeClock) {
this.fakeClock = fakeClock;
}
@Singleton
@Provides
static ProxyConfig provideProxyConfig() {
return getProxyConfig(TEST);
}
@Singleton
@Provides
static SslProvider provideSslProvider() {
return SslProvider.JDK;
}
@Singleton
@Provides
@Named("accessToken")
static Supplier<String> provideFakeAccessToken() {
return Suppliers.ofInstance("fake.test.token");
}
@Singleton
@Provides
static LoggingHandler provideLoggingHandler() {
return new LoggingHandler();
}
@Singleton
@Provides
static SelfSignedCertificate provideSelfSignedCertificate() {
try {
return new SelfSignedCertificate();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Singleton
@Provides
@Named("eppServerCertificates")
static X509Certificate[] provideCertificate(SelfSignedCertificate ssc) {
return new X509Certificate[] {ssc.cert()};
}
@Singleton
@Provides
static PrivateKey providePrivateKey(SelfSignedCertificate ssc) {
return ssc.key();
}
@Singleton
@Provides
Clock provideFakeClock() {
return fakeClock;
}
@Singleton
@Provides
ExecutorService provideExecutorService() {
return MoreExecutors.newDirectExecutorService();
}
@Singleton
@Provides
ScheduledExecutorService provideScheduledExecutorService() {
return Executors.newSingleThreadScheduledExecutor();
}
}
}