1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Add canary service to GKE (#2594)

This commit is contained in:
Lai Jiang
2024-10-22 13:12:00 -04:00
committed by GitHub
parent 4d96e5a6b1
commit a9ba770bfa
20 changed files with 368 additions and 49 deletions

View File

@@ -21,6 +21,7 @@ import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.converters.IParameterSplitter;
import com.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -103,7 +104,10 @@ class CurlCommand implements CommandWithConnection {
throw new IllegalArgumentException("You may not specify a body for a get method.");
}
Service service = useGke ? GkeService.valueOf(serviceName) : GaeService.valueOf(serviceName);
Service service =
useGke
? GkeService.valueOf(Ascii.toUpperCase(serviceName))
: GaeService.valueOf(Ascii.toUpperCase(serviceName));
ServiceConnection connectionToService = connection.withService(service, canary);
String response =

View File

@@ -14,7 +14,6 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Verify.verify;
@@ -35,7 +34,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.CharStreams;
import com.google.common.net.MediaType;
import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
import google.registry.config.RegistryConfig.Config;
import google.registry.request.Action.GaeService;
@@ -60,6 +58,8 @@ public class ServiceConnection {
/** Pattern to heuristically extract title tag contents in HTML responses. */
protected static final Pattern HTML_TITLE_TAG_PATTERN = Pattern.compile("<title>(.*?)</title>");
private static final String CANARY_HEADER = "canary";
private final Service service;
private final boolean useCanary;
private final HttpRequestFactory requestFactory;
@@ -70,9 +70,6 @@ public class ServiceConnection {
}
private ServiceConnection(Service service, HttpRequestFactory requestFactory, boolean useCanary) {
// Currently, only GAE supports connecting to canary.
// TODO (jianglai): decide how to implement canary for GKE.
checkArgument(useCanary == false || service instanceof GaeService, "Canary is only for GAE");
this.service = service;
this.requestFactory = requestFactory;
this.useCanary = useCanary;
@@ -80,15 +77,17 @@ public class ServiceConnection {
/** Returns a copy of this connection that talks to a different service endpoint. */
public ServiceConnection withService(Service service, boolean useCanary) {
Class<? extends Service> oldServiceClazz = this.service.getClass();
Class<? extends Service> newServiceClazz = service.getClass();
if (oldServiceClazz != newServiceClazz) {
throw new IllegalArgumentException(
String.format(
"Cannot switch from %s to %s",
oldServiceClazz.getSimpleName(), newServiceClazz.getSimpleName()));
}
return new ServiceConnection(service, requestFactory, useCanary);
}
/** Returns the contents of the title tag in the given HTML, or null if not found. */
private static String extractHtmlTitle(String html) {
Matcher matcher = HTML_TITLE_TAG_PATTERN.matcher(html);
return (matcher.find() ? matcher.group(1) : null);
}
/** Returns the HTML from the connection error stream, if any, otherwise the empty string. */
private static String getErrorHtmlAsString(HttpResponse response) throws IOException {
return CharStreams.toString(new InputStreamReader(response.getContent(), UTF_8));
@@ -107,19 +106,22 @@ public class ServiceConnection {
HttpHeaders headers = request.getHeaders();
headers.setCacheControl("no-cache");
headers.put(X_REQUESTED_WITH, ImmutableList.of("RegistryTool"));
if (useCanary) {
headers.set(CANARY_HEADER, "true");
}
request.setHeaders(headers);
request.setFollowRedirects(false);
request.setThrowExceptionOnExecuteError(false);
request.setUnsuccessfulResponseHandler(
(request1, response, supportsRetry) -> {
String errorTitle = extractHtmlTitle(getErrorHtmlAsString(response));
String error = getErrorHtmlAsString(response);
throw new IOException(
String.format(
"Error from %s: %d %s%s",
request1.getUrl().toString(),
response.getStatusCode(),
response.getStatusMessage(),
(errorTitle == null ? "" : ": " + errorTitle)));
error));
});
HttpResponse response = null;
try {
@@ -135,8 +137,8 @@ public class ServiceConnection {
@VisibleForTesting
URL getServer() {
URL url = service.getServiceUrl();
if (useCanary) {
verify(!isNullOrEmpty(url.getHost()), "Null host in url");
verify(!isNullOrEmpty(url.getHost()), "Null host in url");
if (useCanary && service instanceof GaeService) {
url =
makeUrl(
String.format(

View File

@@ -16,23 +16,65 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.Action.GaeService.DEFAULT;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.common.collect.ImmutableMap;
import google.registry.request.Action.GkeService;
import java.io.ByteArrayInputStream;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link google.registry.tools.ServiceConnection}. */
public class ServiceConnectionTest {
@Test
void testServerUrl_notCanary() {
void testSuccess_serverUrl_notCanary() {
ServiceConnection connection = new ServiceConnection(false, null).withService(DEFAULT, false);
String serverUrl = connection.getServer().toString();
assertThat(serverUrl).isEqualTo("https://default.example.com"); // See default-config.yaml
}
@Test
void testServerUrl_canary() {
void testFailure_mixedService() throws Exception {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> {
new ServiceConnection(true, null).withService(DEFAULT, true);
});
assertThat(thrown).hasMessageThat().contains("Cannot switch from GkeService to GaeService");
}
@Test
void testSuccess_serverUrl_gae_canary() {
ServiceConnection connection = new ServiceConnection(false, null).withService(DEFAULT, true);
String serverUrl = connection.getServer().toString();
assertThat(serverUrl).isEqualTo("https://nomulus-dot-default.example.com");
}
@Test
void testSuccess_serverUrl_gke_canary() throws Exception {
HttpRequestFactory factory = mock(HttpRequestFactory.class);
HttpRequest request = mock(HttpRequest.class);
HttpHeaders headers = mock(HttpHeaders.class);
HttpResponse response = mock(HttpResponse.class);
when(request.getHeaders()).thenReturn(headers);
when(factory.buildGetRequest(any(GenericUrl.class))).thenReturn(request);
when(request.execute()).thenReturn(response);
when(response.getContent()).thenReturn(ByteArrayInputStream.nullInputStream());
ServiceConnection connection =
new ServiceConnection(true, factory).withService(GkeService.PUBAPI, true);
String serverUrl = connection.getServer().toString();
assertThat(serverUrl).isEqualTo("https://pubapi.registry.test");
connection.sendGetRequest("/path", ImmutableMap.of());
verify(headers).set("canary", "true");
}
}

View File

@@ -37,6 +37,11 @@ do
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
kubectl apply -f -
# canary
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
sed s/"${service}"/"${service}-canary"/g | \
kubectl apply -f -
done
# Kills all running pods, new pods created will be pulling the new image.
kubectl delete pods --all

View File

@@ -30,6 +30,42 @@ spec:
kind: ServiceImport
name: backend
port: 80
- matches:
- path:
type: PathPrefix
value: /_dr/task
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /_dr/cron
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /_dr/admin
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /_dr/epptool
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /loadtest
headers:
- name: "canary"
value: "true"
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: backend-canary
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
@@ -45,3 +81,18 @@ spec:
group: net.gke.io
kind: ServiceImport
name: backend
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: backend-canary
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: backend-canary

View File

@@ -21,6 +21,24 @@ spec:
kind: ServiceImport
name: console
port: 80
- matches:
- path:
type: PathPrefix
value: /console-api
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /console
headers:
- name: "canary"
value: "true"
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: console-canary
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
@@ -36,3 +54,18 @@ spec:
group: net.gke.io
kind: ServiceImport
name: console
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: console-canary
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: console-canary

View File

@@ -18,6 +18,18 @@ spec:
kind: ServiceImport
name: frontend
port: 80
- matches:
- path:
type: PathPrefix
value: /_dr/epp
headers:
- name: "canary"
value: "true"
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: frontend-canary
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
@@ -33,3 +45,18 @@ spec:
group: net.gke.io
kind: ServiceImport
name: frontend
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: frontend-canary
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: frontend-canary

View File

@@ -27,6 +27,36 @@ spec:
kind: ServiceImport
name: pubapi
port: 80
- matches:
- path:
type: PathPrefix
value: /_dr/whois
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /check
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /whois
headers:
- name: "canary"
value: "true"
- path:
type: PathPrefix
value: /rdap
headers:
- name: "canary"
value: "true"
backendRefs:
- group: net.gke.io
kind: ServiceImport
name: pubapi-canary
port: 80
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
@@ -42,3 +72,18 @@ spec:
group: net.gke.io
kind: ServiceImport
name: pubapi
---
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: pubapi-canary
spec:
default:
config:
type: HTTP
httpHealthCheck:
requestPath: /healthz/
targetRef:
group: net.gke.io
kind: ServiceImport
name: pubapi-canary

View File

@@ -151,12 +151,14 @@ public final class EppProtocolModule {
static EppServiceHandler provideEppServiceHandler(
@Named("idToken") Supplier<String> idTokenSupplier,
@Named("hello") byte[] helloBytes,
@Named("canary") boolean canary,
FrontendMetrics metrics,
ProxyConfig config,
@HttpsRelayProtocol boolean localRelay) {
return new EppServiceHandler(
localRelay ? "localhost" : config.epp.relayHost,
config.epp.relayPath,
canary,
idTokenSupplier,
helloBytes,
metrics);

View File

@@ -41,6 +41,7 @@ public class ProxyConfig {
public String projectId;
public String oauthClientId;
public boolean canary;
public List<String> gcpScopes;
public int serverCertificateCacheSeconds;
public Gcs gcs;

View File

@@ -278,6 +278,13 @@ public class ProxyModule {
() -> OidcTokenUtils.createOidcToken(credentialsBundle, clientId), 1, TimeUnit.HOURS);
}
@Singleton
@Provides
@Named("canary")
static boolean provideIsCanary(ProxyConfig config) {
return config.canary;
}
@Singleton
@Provides
static CloudKMS provideCloudKms(GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {

View File

@@ -96,11 +96,13 @@ public final class WhoisProtocolModule {
static WhoisServiceHandler provideWhoisServiceHandler(
ProxyConfig config,
@Named("idToken") Supplier<String> idTokenSupplier,
@Named("canary") boolean canary,
FrontendMetrics metrics,
@HttpsRelayProtocol boolean localRelay) {
return new WhoisServiceHandler(
localRelay ? "localhost" : config.whois.relayHost,
config.whois.relayPath,
canary,
idTokenSupplier,
metrics);
}

View File

@@ -8,6 +8,9 @@
# GCP project ID
projectId: your-gcp-project-id
# Whether to connect to the canary (instead of regular) service.
canary: false
# OAuth client ID set as the audience of the OIDC token. This value must be the
# same as the auth.oauthClientId value in Nomulus config file, which usually is
# the IAP client ID, to allow the request to access IAP protected endpoints.

View File

@@ -60,10 +60,11 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
public EppServiceHandler(
String relayHost,
String relayPath,
boolean canary,
Supplier<String> idTokenSupplier,
byte[] helloBytes,
FrontendMetrics metrics) {
super(relayHost, relayPath, idTokenSupplier, metrics);
super(relayHost, relayPath, canary, idTokenSupplier, metrics);
this.helloBytes = helloBytes.clone();
}

View File

@@ -62,6 +62,7 @@ import javax.net.ssl.SSLHandshakeException;
public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpResponse> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String CANARY_HEADER = "canary";
protected static final ImmutableSet<Class<? extends Exception>> NON_FATAL_INBOUND_EXCEPTIONS =
ImmutableSet.of(ReadTimeoutException.class, SSLHandshakeException.class);
@@ -72,6 +73,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
private final String relayHost;
private final String relayPath;
private final boolean canary;
private final Supplier<String> idTokenSupplier;
protected final FrontendMetrics metrics;
@@ -79,10 +81,12 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
HttpsRelayServiceHandler(
String relayHost,
String relayPath,
boolean canary,
Supplier<String> idTokenSupplier,
FrontendMetrics metrics) {
this.relayHost = relayHost;
this.relayPath = relayPath;
this.canary = canary;
this.idTokenSupplier = idTokenSupplier;
this.metrics = metrics;
}
@@ -104,6 +108,9 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
.set(HttpHeaderNames.HOST, relayHost)
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + idTokenSupplier.get())
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
if (canary) {
request.headers().set(CANARY_HEADER, "true");
}
request.content().writeBytes(byteBuf);
return request;
}

View File

@@ -33,9 +33,10 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
public WhoisServiceHandler(
String relayHost,
String relayPath,
boolean canary,
Supplier<String> idTokenSupplier,
FrontendMetrics metrics) {
super(relayHost, relayPath, idTokenSupplier, metrics);
super(relayHost, relayPath, canary, idTokenSupplier, metrics);
}
@Override

View File

@@ -261,6 +261,13 @@ public abstract class ProtocolModuleTest {
return Suppliers.ofInstance("fake.test.id.token");
}
@Singleton
@Provides
@Named("canary")
static boolean provideIsCanary() {
return false;
}
@Singleton
@Provides
static LoggingHandler provideLoggingHandler() {

View File

@@ -25,7 +25,6 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
@@ -53,6 +52,22 @@ public final class TestUtils {
return request;
}
public static FullHttpRequest makeHttpPostRequest(
String content, String host, String path, boolean canary) {
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
FullHttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, path, buf);
request
.headers()
.set("user-agent", "Proxy")
.set("host", host)
.setInt("content-length", buf.readableBytes());
if (canary) {
request.headers().set("canary", "true");
}
return request;
}
public static FullHttpRequest makeHttpGetRequest(String host, String path) {
FullHttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
@@ -74,16 +89,46 @@ public final class TestUtils {
}
public static FullHttpRequest makeWhoisHttpRequest(
String content, String host, String path, String idToken) throws IOException {
FullHttpRequest request = makeHttpPostRequest(content, host, path);
String content, String host, String path, boolean canary, String idToken) throws IOException {
FullHttpRequest request = makeHttpPostRequest(content, host, path, canary);
request
.headers()
.set("authorization", "Bearer " + idToken)
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
.set("content-type", "text/plain")
.set("accept", "text/plain");
return request;
}
public static FullHttpRequest makeWhoisHttpRequest(
String content, String host, String path, String idToken) throws IOException {
return makeWhoisHttpRequest(content, host, path, false, idToken);
}
public static FullHttpRequest makeEppHttpRequest(
String content,
String host,
String path,
boolean canary,
String idToken,
String sslClientCertificateHash,
String clientAddress,
Cookie... cookies)
throws IOException {
FullHttpRequest request = makeHttpPostRequest(content, host, path, canary);
request
.headers()
.set("authorization", "Bearer " + idToken)
.set("content-type", "application/epp+xml")
.set("accept", "application/epp+xml")
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
if (cookies.length != 0) {
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
}
return request;
}
public static FullHttpRequest makeEppHttpRequest(
String content,
String host,
@@ -93,31 +138,20 @@ public final class TestUtils {
String clientAddress,
Cookie... cookies)
throws IOException {
FullHttpRequest request = makeHttpPostRequest(content, host, path);
request
.headers()
.set("authorization", "Bearer " + idToken)
.set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml")
.set("accept", "application/epp+xml")
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
if (cookies.length != 0) {
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
}
return request;
return makeEppHttpRequest(
content, host, path, false, idToken, sslClientCertificateHash, clientAddress, cookies);
}
public static FullHttpResponse makeWhoisHttpResponse(String content, HttpResponseStatus status) {
FullHttpResponse response = makeHttpResponse(content, status);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set("content-type", "text/plain");
return response;
}
public static FullHttpResponse makeEppHttpResponse(
String content, HttpResponseStatus status, Cookie... cookies) {
FullHttpResponse response = makeHttpResponse(content, status);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml");
response.headers().set("content-type", "application/epp+xml");
for (Cookie cookie : cookies) {
response.headers().add("set-cookie", ServerCookieEncoder.STRICT.encode(cookie));
}

View File

@@ -54,11 +54,11 @@ class EppServiceHandlerTest {
private static final String HELLO =
"""
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello/>
</epp>
""";
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello/>
</epp>
""";
private static final String RELAY_HOST = "registry.example.tld";
private static final String RELAY_PATH = "/epp";
@@ -71,7 +71,8 @@ class EppServiceHandlerTest {
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
private final EppServiceHandler eppServiceHandler =
new EppServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
new EppServiceHandler(
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
private EmbeddedChannel channel;
@@ -146,7 +147,7 @@ class EppServiceHandlerTest {
// Set up the second channel.
EppServiceHandler eppServiceHandler2 =
new EppServiceHandler(
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
setHandshakeSuccess(channel2, clientCertificate);
@@ -166,7 +167,7 @@ class EppServiceHandlerTest {
// Set up the second channel.
EppServiceHandler eppServiceHandler2 =
new EppServiceHandler(
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
setHandshakeSuccess(channel2, clientCertificate2);
@@ -214,6 +215,33 @@ class EppServiceHandlerTest {
assertThat(channel.isActive()).isTrue();
}
@Test
void testSuccess_sendRequestToNextHandler_canary() throws Exception {
EppServiceHandler eppServiceHandler2 =
new EppServiceHandler(
RELAY_HOST, RELAY_PATH, true, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
channel = setUpNewChannel(eppServiceHandler2);
setHandshakeSuccess();
// First inbound message is hello.
channel.readInbound();
String content = "<epp>stuff</epp>";
channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8)));
FullHttpRequest request = channel.readInbound();
assertThat(request)
.isEqualTo(
TestUtils.makeEppHttpRequest(
content,
RELAY_HOST,
RELAY_PATH,
true,
ID_TOKEN,
getCertificateHash(clientCertificate),
CLIENT_ADDRESS));
// Nothing further to pass to the next handler.
assertThat((Object) channel.readInbound()).isNull();
assertThat(channel.isActive()).isTrue();
}
@Test
void testSuccess_sendResponseToNextHandler() throws Exception {
setHandshakeSuccess();

View File

@@ -50,7 +50,7 @@ class WhoisServiceHandlerTest {
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
private final WhoisServiceHandler whoisServiceHandler =
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, metrics);
private EmbeddedChannel channel;
@BeforeEach
@@ -74,7 +74,7 @@ class WhoisServiceHandlerTest {
// Setup second channel.
WhoisServiceHandler whoisServiceHandler2 =
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, metrics);
EmbeddedChannel channel2 =
// We need a new channel id so that it has a different hash code.
// This only is needed for EmbeddedChannel because it has a dummy hash code implementation.
@@ -98,6 +98,23 @@ class WhoisServiceHandlerTest {
assertThat(channel.isActive()).isTrue();
}
@Test
void testSuccess_fireInboundHttpRequest_canary() throws Exception {
WhoisServiceHandler whoisServiceHandler2 =
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, true, () -> ID_TOKEN, metrics);
channel = new EmbeddedChannel(whoisServiceHandler2);
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
FullHttpRequest expectedRequest =
makeWhoisHttpRequest(QUERY_CONTENT, RELAY_HOST, RELAY_PATH, true, ID_TOKEN);
// Input data passed to next handler
assertThat(channel.writeInbound(inputBuffer)).isTrue();
FullHttpRequest inputRequest = channel.readInbound();
assertThat(inputRequest).isEqualTo(expectedRequest);
// The channel is still open, and nothing else is to be read from it.
assertThat((Object) channel.readInbound()).isNull();
assertThat(channel.isActive()).isTrue();
}
@Test
void testSuccess_parseOutboundHttpResponse() {
String outputString = "line1\r\nline2\r\n";