mirror of
https://github.com/google/nomulus.git
synced 2025-07-06 19:23:31 +02:00
Prober metrics collection (#222)
* Added MetricsHandler and Clock to ProbingSequences * Minor fixes after rebase onto master * Added metrics gathering to ProbingSequences * Added testing of MetricsCollector method calls in ProbingSequenceTest * Added tests on latency recording to ProbingSequenceTest * Added response name as label to metrics
This commit is contained in:
parent
92f2f3274e
commit
3ac179aead
13 changed files with 490 additions and 35 deletions
|
@ -25,6 +25,7 @@ dependencies {
|
||||||
compile deps['com.google.dagger:dagger']
|
compile deps['com.google.dagger:dagger']
|
||||||
compile deps['com.google.flogger:flogger']
|
compile deps['com.google.flogger:flogger']
|
||||||
compile deps['com.google.guava:guava']
|
compile deps['com.google.guava:guava']
|
||||||
|
compile deps['com.google.monitoring-client:metrics']
|
||||||
compile deps['io.netty:netty-buffer']
|
compile deps['io.netty:netty-buffer']
|
||||||
compile deps['io.netty:netty-codec-http']
|
compile deps['io.netty:netty-codec-http']
|
||||||
compile deps['io.netty:netty-codec']
|
compile deps['io.netty:netty-codec']
|
||||||
|
@ -41,10 +42,12 @@ dependencies {
|
||||||
runtime deps['com.google.auto.value:auto-value']
|
runtime deps['com.google.auto.value:auto-value']
|
||||||
runtime deps['io.netty:netty-tcnative-boringssl-static']
|
runtime deps['io.netty:netty-tcnative-boringssl-static']
|
||||||
|
|
||||||
|
testCompile deps['com.google.monitoring-client:contrib']
|
||||||
testCompile deps['com.google.truth:truth']
|
testCompile deps['com.google.truth:truth']
|
||||||
testCompile deps['junit:junit']
|
testCompile deps['junit:junit']
|
||||||
testCompile deps['org.mockito:mockito-core']
|
testCompile deps['org.mockito:mockito-core']
|
||||||
testCompile project(':third_party')
|
testCompile project(':third_party')
|
||||||
|
testCompile project(path: ':core', configuration: 'testRuntime')
|
||||||
|
|
||||||
// Include auto-value in compile until nebula-lint understands
|
// Include auto-value in compile until nebula-lint understands
|
||||||
// annotationProcessor
|
// annotationProcessor
|
||||||
|
|
|
@ -21,6 +21,8 @@ import google.registry.monitoring.blackbox.connection.ProbingAction;
|
||||||
import google.registry.monitoring.blackbox.modules.CertificateModule;
|
import google.registry.monitoring.blackbox.modules.CertificateModule;
|
||||||
import google.registry.monitoring.blackbox.modules.EppModule;
|
import google.registry.monitoring.blackbox.modules.EppModule;
|
||||||
import google.registry.monitoring.blackbox.modules.WebWhoisModule;
|
import google.registry.monitoring.blackbox.modules.WebWhoisModule;
|
||||||
|
import google.registry.util.Clock;
|
||||||
|
import google.registry.util.SystemClock;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
@ -54,6 +56,13 @@ public class ProberModule {
|
||||||
return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
|
return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@link Provides} one global {@link Clock} shared by each {@link ProbingSequence}. */
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Clock provideClock() {
|
||||||
|
return new SystemClock();
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Provides} one global {@link EventLoopGroup} shared by each {@link ProbingSequence}. */
|
/** {@link Provides} one global {@link EventLoopGroup} shared by each {@link ProbingSequence}. */
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
@ -16,9 +16,12 @@ package google.registry.monitoring.blackbox;
|
||||||
|
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.monitoring.blackbox.connection.ProbingAction;
|
import google.registry.monitoring.blackbox.connection.ProbingAction;
|
||||||
|
import google.registry.monitoring.blackbox.exceptions.FailureException;
|
||||||
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
|
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
|
||||||
|
import google.registry.monitoring.blackbox.metrics.MetricsCollector;
|
||||||
import google.registry.monitoring.blackbox.tokens.Token;
|
import google.registry.monitoring.blackbox.tokens.Token;
|
||||||
import google.registry.util.CircularList;
|
import google.registry.util.CircularList;
|
||||||
|
import google.registry.util.Clock;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.channel.AbstractChannel;
|
import io.netty.channel.AbstractChannel;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
|
@ -45,6 +48,12 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
/** Shared {@link MetricsCollector} used to record metrics on any step performed. */
|
||||||
|
private MetricsCollector metrics;
|
||||||
|
|
||||||
|
/** Shared {@link Clock} used to record latency on any step performed. */
|
||||||
|
private Clock clock;
|
||||||
|
|
||||||
/** Each {@link ProbingSequence} requires a start token to begin running. */
|
/** Each {@link ProbingSequence} requires a start token to begin running. */
|
||||||
private Token startToken;
|
private Token startToken;
|
||||||
|
|
||||||
|
@ -57,9 +66,16 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
/** {@link ProbingSequence} object that represents first step in the sequence. */
|
/** {@link ProbingSequence} object that represents first step in the sequence. */
|
||||||
private ProbingSequence first;
|
private ProbingSequence first;
|
||||||
|
|
||||||
/** Standard constructor for {@link ProbingSequence} in the list that assigns value and token. */
|
/**
|
||||||
private ProbingSequence(ProbingStep value, Token startToken) {
|
* Standard constructor for first {@link ProbingSequence} in the list that assigns value and
|
||||||
|
* token.
|
||||||
|
*/
|
||||||
|
private ProbingSequence(
|
||||||
|
ProbingStep value, MetricsCollector metrics, Clock clock, Token startToken) {
|
||||||
|
|
||||||
super(value);
|
super(value);
|
||||||
|
this.metrics = metrics;
|
||||||
|
this.clock = clock;
|
||||||
this.startToken = startToken;
|
this.startToken = startToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +111,8 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
* get().generateAction}.
|
* get().generateAction}.
|
||||||
*/
|
*/
|
||||||
private void runStep(Token token) {
|
private void runStep(Token token) {
|
||||||
|
long start = clock.nowUtc().getMillis();
|
||||||
|
|
||||||
ProbingAction currentAction;
|
ProbingAction currentAction;
|
||||||
ChannelFuture future;
|
ChannelFuture future;
|
||||||
|
|
||||||
|
@ -108,12 +126,28 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
} catch (UnrecoverableStateException e) {
|
} catch (UnrecoverableStateException e) {
|
||||||
// On an UnrecoverableStateException, terminate the sequence.
|
// On an UnrecoverableStateException, terminate the sequence.
|
||||||
logger.atSevere().withCause(e).log("Unrecoverable error in generating or calling action.");
|
logger.atSevere().withCause(e).log("Unrecoverable error in generating or calling action.");
|
||||||
|
|
||||||
|
// Records gathered metrics.
|
||||||
|
metrics.recordResult(
|
||||||
|
get().protocol().name(),
|
||||||
|
get().messageTemplate().name(),
|
||||||
|
get().messageTemplate().responseName(),
|
||||||
|
MetricsCollector.ResponseType.ERROR,
|
||||||
|
clock.nowUtc().getMillis() - start);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// On any other type of error, restart the sequence at the very first step.
|
// On any other type of error, restart the sequence at the very first step.
|
||||||
logger.atWarning().withCause(e).log("Error in generating or calling action.");
|
logger.atWarning().withCause(e).log("Error in generating or calling action.");
|
||||||
|
|
||||||
|
// Records gathered metrics.
|
||||||
|
metrics.recordResult(
|
||||||
|
get().protocol().name(),
|
||||||
|
get().messageTemplate().name(),
|
||||||
|
get().messageTemplate().responseName(),
|
||||||
|
MetricsCollector.ResponseType.ERROR,
|
||||||
|
clock.nowUtc().getMillis() - start);
|
||||||
|
|
||||||
// Restart the sequence at the very first step.
|
// Restart the sequence at the very first step.
|
||||||
restartSequence();
|
restartSequence();
|
||||||
return;
|
return;
|
||||||
|
@ -125,10 +159,34 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
// On a successful result, we log as a successful step, and note a success.
|
// On a successful result, we log as a successful step, and note a success.
|
||||||
logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this));
|
logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this));
|
||||||
|
|
||||||
|
// Records gathered metrics.
|
||||||
|
metrics.recordResult(
|
||||||
|
get().protocol().name(),
|
||||||
|
get().messageTemplate().name(),
|
||||||
|
get().messageTemplate().responseName(),
|
||||||
|
MetricsCollector.ResponseType.SUCCESS,
|
||||||
|
clock.nowUtc().getMillis() - start);
|
||||||
} else {
|
} else {
|
||||||
// On a failed result, we log the failure and note either a failure or error.
|
// On a failed result, we log the failure and note either a failure or error.
|
||||||
logger.atSevere().withCause(f.cause()).log("Did not result in future success");
|
logger.atSevere().withCause(f.cause()).log("Did not result in future success");
|
||||||
|
|
||||||
|
// Records gathered metrics as either FAILURE or ERROR depending on future's cause.
|
||||||
|
if (f.cause() instanceof FailureException) {
|
||||||
|
metrics.recordResult(
|
||||||
|
get().protocol().name(),
|
||||||
|
get().messageTemplate().name(),
|
||||||
|
get().messageTemplate().responseName(),
|
||||||
|
MetricsCollector.ResponseType.FAILURE,
|
||||||
|
clock.nowUtc().getMillis() - start);
|
||||||
|
} else {
|
||||||
|
metrics.recordResult(
|
||||||
|
get().protocol().name(),
|
||||||
|
get().messageTemplate().name(),
|
||||||
|
get().messageTemplate().responseName(),
|
||||||
|
MetricsCollector.ResponseType.ERROR,
|
||||||
|
clock.nowUtc().getMillis() - start);
|
||||||
|
}
|
||||||
|
|
||||||
// If not unrecoverable, we restart the sequence.
|
// If not unrecoverable, we restart the sequence.
|
||||||
if (!(f.cause() instanceof UnrecoverableStateException)) {
|
if (!(f.cause() instanceof UnrecoverableStateException)) {
|
||||||
restartSequence();
|
restartSequence();
|
||||||
|
@ -181,12 +239,18 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
|
|
||||||
private Token startToken;
|
private Token startToken;
|
||||||
|
|
||||||
|
private MetricsCollector metrics;
|
||||||
|
|
||||||
|
private Clock clock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This Builder must also be supplied with a {@link Token} to construct a {@link
|
* This Builder must also be supplied with a {@link Token} to construct a {@link
|
||||||
* ProbingSequence}.
|
* ProbingSequence}.
|
||||||
*/
|
*/
|
||||||
public Builder(Token startToken) {
|
public Builder(Token startToken, MetricsCollector metrics, Clock clock) {
|
||||||
this.startToken = startToken;
|
this.startToken = startToken;
|
||||||
|
this.metrics = metrics;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** We take special note of the first repeated step. */
|
/** We take special note of the first repeated step. */
|
||||||
|
@ -205,7 +269,7 @@ public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ProbingSequence create(ProbingStep value) {
|
protected ProbingSequence create(ProbingStep value) {
|
||||||
return new ProbingSequence(value, startToken);
|
return new ProbingSequence(value, metrics, clock, startToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -44,6 +44,12 @@ import java.util.function.BiFunction;
|
||||||
*/
|
*/
|
||||||
public class EppRequestMessage extends EppMessage implements OutboundMessageType {
|
public class EppRequestMessage extends EppMessage implements OutboundMessageType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String that describes the type of EppRequestMessage: hello, login, create, check, delete,
|
||||||
|
* logout.
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
/** Corresponding {@link EppResponseMessage} that we expect to receive on a successful request. */
|
/** Corresponding {@link EppResponseMessage} that we expect to receive on a successful request. */
|
||||||
private EppResponseMessage expectedResponse;
|
private EppResponseMessage expectedResponse;
|
||||||
|
|
||||||
|
@ -60,10 +66,12 @@ public class EppRequestMessage extends EppMessage implements OutboundMessageType
|
||||||
* Private constructor for {@link EppRequestMessage} that its subclasses use for instantiation.
|
* Private constructor for {@link EppRequestMessage} that its subclasses use for instantiation.
|
||||||
*/
|
*/
|
||||||
public EppRequestMessage(
|
public EppRequestMessage(
|
||||||
|
String name,
|
||||||
EppResponseMessage expectedResponse,
|
EppResponseMessage expectedResponse,
|
||||||
String template,
|
String template,
|
||||||
BiFunction<String, String, Map<String, String>> getReplacements) {
|
BiFunction<String, String, Map<String, String>> getReplacements) {
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
this.expectedResponse = expectedResponse;
|
this.expectedResponse = expectedResponse;
|
||||||
this.template = template;
|
this.template = template;
|
||||||
this.getReplacements = getReplacements;
|
this.getReplacements = getReplacements;
|
||||||
|
@ -125,8 +133,18 @@ public class EppRequestMessage extends EppMessage implements OutboundMessageType
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** Returns the {@link EppResponseMessage} we expect. */
|
||||||
public EppResponseMessage getExpectedResponse() {
|
public EppResponseMessage getExpectedResponse() {
|
||||||
return expectedResponse;
|
return expectedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String responseName() {
|
||||||
|
return expectedResponse.name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,12 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String name() {
|
||||||
return String.format("Http(s) Request on: %s", headers().get("host"));
|
return String.format("Http(s) Request on: %s", headers().get("host"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String responseName() {
|
||||||
|
return "Http Response";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,15 @@ public interface OutboundMessageType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Necessary to inform metrics collector what kind of message is sent down {@link
|
* Necessary to inform metrics collector what kind of message is sent down {@link
|
||||||
* io.netty.channel.ChannelPipeline}
|
* io.netty.channel.ChannelPipeline}. Not equivalent to toString, as to different instances will
|
||||||
|
* have the same name if they perform the same action.
|
||||||
*/
|
*/
|
||||||
@Override
|
String name();
|
||||||
String toString();
|
|
||||||
|
/**
|
||||||
|
* Necessary to inform metrics collector what kind of message is sent inbound {@link
|
||||||
|
* io.netty.channel.ChannelPipeline}. Equivalent to {@code name} but for {@link
|
||||||
|
* InboundMessageType}.
|
||||||
|
*/
|
||||||
|
String responseName();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2019 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.monitoring.blackbox.metrics;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.monitoring.metrics.EventMetric;
|
||||||
|
import com.google.monitoring.metrics.ExponentialFitter;
|
||||||
|
import com.google.monitoring.metrics.IncrementableMetric;
|
||||||
|
import com.google.monitoring.metrics.LabelDescriptor;
|
||||||
|
import com.google.monitoring.metrics.MetricRegistryImpl;
|
||||||
|
import google.registry.util.NonFinalForTesting;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/** Metrics collection instrumentation. */
|
||||||
|
@Singleton
|
||||||
|
public class MetricsCollector {
|
||||||
|
|
||||||
|
/** Three standard Response types to be recorded as metrics: SUCCESS, FAILURE, or ERROR. */
|
||||||
|
public enum ResponseType {
|
||||||
|
SUCCESS,
|
||||||
|
FAILURE,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum 1 hour latency, this is not specified by the spec, but given we have a one hour idle
|
||||||
|
// timeout, it seems reasonable that maximum latency is set to 1 hour as well. If we are
|
||||||
|
// approaching anywhere near 1 hour latency, we'd be way out of SLO anyway.
|
||||||
|
private static final ExponentialFitter DEFAULT_LATENCY_FITTER =
|
||||||
|
ExponentialFitter.create(22, 2, 1.0);
|
||||||
|
|
||||||
|
private static final ImmutableSet<LabelDescriptor> LABELS =
|
||||||
|
ImmutableSet.of(
|
||||||
|
LabelDescriptor.create("protocol", "Name of the protocol."),
|
||||||
|
LabelDescriptor.create("request", "Name of outbound request"),
|
||||||
|
LabelDescriptor.create("response", "Name of inbound response"),
|
||||||
|
LabelDescriptor.create("responseType", "Status of action performed"));
|
||||||
|
|
||||||
|
static final IncrementableMetric responsesCounter =
|
||||||
|
MetricRegistryImpl.getDefault()
|
||||||
|
.newIncrementableMetric(
|
||||||
|
"/prober/responses",
|
||||||
|
"Total number of responses received by the prober.",
|
||||||
|
"Responses",
|
||||||
|
LABELS);
|
||||||
|
|
||||||
|
static final EventMetric latencyMs =
|
||||||
|
MetricRegistryImpl.getDefault()
|
||||||
|
.newEventMetric(
|
||||||
|
"/prober/latency_specific_ms",
|
||||||
|
"Round-trip time between a request sent and its corresponding response received.",
|
||||||
|
"Latency Milliseconds",
|
||||||
|
LABELS,
|
||||||
|
DEFAULT_LATENCY_FITTER);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricsCollector() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all backend metrics.
|
||||||
|
*
|
||||||
|
* <p>This should only used in tests to clear out states. No production code should call this
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
void resetMetric() {
|
||||||
|
responsesCounter.reset();
|
||||||
|
latencyMs.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonFinalForTesting
|
||||||
|
public void recordResult(
|
||||||
|
String protocolName,
|
||||||
|
String requestName,
|
||||||
|
String responseName,
|
||||||
|
ResponseType status,
|
||||||
|
long latency) {
|
||||||
|
latencyMs.record(latency, protocolName, requestName, responseName, status.name());
|
||||||
|
responsesCounter.increment(protocolName, requestName, responseName, status.name());
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,8 +34,10 @@ import google.registry.monitoring.blackbox.handlers.SslClientInitializer;
|
||||||
import google.registry.monitoring.blackbox.messages.EppMessage;
|
import google.registry.monitoring.blackbox.messages.EppMessage;
|
||||||
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
|
import google.registry.monitoring.blackbox.messages.EppRequestMessage;
|
||||||
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
|
import google.registry.monitoring.blackbox.messages.EppResponseMessage;
|
||||||
|
import google.registry.monitoring.blackbox.metrics.MetricsCollector;
|
||||||
import google.registry.monitoring.blackbox.modules.CertificateModule.LocalSecrets;
|
import google.registry.monitoring.blackbox.modules.CertificateModule.LocalSecrets;
|
||||||
import google.registry.monitoring.blackbox.tokens.EppToken;
|
import google.registry.monitoring.blackbox.tokens.EppToken;
|
||||||
|
import google.registry.util.Clock;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
@ -86,13 +88,15 @@ public class EppModule {
|
||||||
@IntoSet
|
@IntoSet
|
||||||
static ProbingSequence provideEppLoginCreateCheckDeleteCheckProbingSequence(
|
static ProbingSequence provideEppLoginCreateCheckDeleteCheckProbingSequence(
|
||||||
EppToken.Persistent token,
|
EppToken.Persistent token,
|
||||||
|
MetricsCollector metrics,
|
||||||
|
Clock clock,
|
||||||
@Named("hello") ProbingStep helloStep,
|
@Named("hello") ProbingStep helloStep,
|
||||||
@Named("loginSuccess") ProbingStep loginSuccessStep,
|
@Named("loginSuccess") ProbingStep loginSuccessStep,
|
||||||
@Named("createSuccess") ProbingStep createSuccessStep,
|
@Named("createSuccess") ProbingStep createSuccessStep,
|
||||||
@Named("checkExists") ProbingStep checkStepFirst,
|
@Named("checkExists") ProbingStep checkStepFirst,
|
||||||
@Named("deleteSuccess") ProbingStep deleteSuccessStep,
|
@Named("deleteSuccess") ProbingStep deleteSuccessStep,
|
||||||
@Named("checkNotExists") ProbingStep checkStepSecond) {
|
@Named("checkNotExists") ProbingStep checkStepSecond) {
|
||||||
return new ProbingSequence.Builder(token)
|
return new ProbingSequence.Builder(token, metrics, clock)
|
||||||
.add(helloStep)
|
.add(helloStep)
|
||||||
.add(loginSuccessStep)
|
.add(loginSuccessStep)
|
||||||
.add(createSuccessStep)
|
.add(createSuccessStep)
|
||||||
|
@ -112,6 +116,8 @@ public class EppModule {
|
||||||
@IntoSet
|
@IntoSet
|
||||||
static ProbingSequence provideEppLoginCreateCheckDeleteCheckLogoutProbingSequence(
|
static ProbingSequence provideEppLoginCreateCheckDeleteCheckLogoutProbingSequence(
|
||||||
EppToken.Transient token,
|
EppToken.Transient token,
|
||||||
|
MetricsCollector metrics,
|
||||||
|
Clock clock,
|
||||||
@Named("hello") ProbingStep helloStep,
|
@Named("hello") ProbingStep helloStep,
|
||||||
@Named("loginSuccess") ProbingStep loginSuccessStep,
|
@Named("loginSuccess") ProbingStep loginSuccessStep,
|
||||||
@Named("createSuccess") ProbingStep createSuccessStep,
|
@Named("createSuccess") ProbingStep createSuccessStep,
|
||||||
|
@ -119,7 +125,7 @@ public class EppModule {
|
||||||
@Named("deleteSuccess") ProbingStep deleteSuccessStep,
|
@Named("deleteSuccess") ProbingStep deleteSuccessStep,
|
||||||
@Named("checkNotExists") ProbingStep checkStepSecond,
|
@Named("checkNotExists") ProbingStep checkStepSecond,
|
||||||
@Named("logout") ProbingStep logoutStep) {
|
@Named("logout") ProbingStep logoutStep) {
|
||||||
return new ProbingSequence.Builder(token)
|
return new ProbingSequence.Builder(token, metrics, clock)
|
||||||
.add(helloStep)
|
.add(helloStep)
|
||||||
.add(loginSuccessStep)
|
.add(loginSuccessStep)
|
||||||
.add(createSuccessStep)
|
.add(createSuccessStep)
|
||||||
|
@ -253,7 +259,7 @@ public class EppModule {
|
||||||
static EppRequestMessage provideHelloRequestMessage(
|
static EppRequestMessage provideHelloRequestMessage(
|
||||||
@Named("greeting") EppResponseMessage greetingResponse) {
|
@Named("greeting") EppResponseMessage greetingResponse) {
|
||||||
|
|
||||||
return new EppRequestMessage(greetingResponse, null, (a, b) -> ImmutableMap.of());
|
return new EppRequestMessage("hello", greetingResponse, null, (a, b) -> ImmutableMap.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -270,6 +276,7 @@ public class EppModule {
|
||||||
@Named("eppUserId") String userId,
|
@Named("eppUserId") String userId,
|
||||||
@Named("eppPassword") String userPassword) {
|
@Named("eppPassword") String userPassword) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"login",
|
||||||
successResponse,
|
successResponse,
|
||||||
loginTemplate,
|
loginTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -288,6 +295,7 @@ public class EppModule {
|
||||||
@Named("eppUserId") String userId,
|
@Named("eppUserId") String userId,
|
||||||
@Named("eppPassword") String userPassword) {
|
@Named("eppPassword") String userPassword) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"login",
|
||||||
failureResponse,
|
failureResponse,
|
||||||
loginTemplate,
|
loginTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -304,6 +312,7 @@ public class EppModule {
|
||||||
@Named("success") EppResponseMessage successResponse,
|
@Named("success") EppResponseMessage successResponse,
|
||||||
@Named("create") String createTemplate) {
|
@Named("create") String createTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"create",
|
||||||
successResponse,
|
successResponse,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -319,6 +328,7 @@ public class EppModule {
|
||||||
@Named("failure") EppResponseMessage failureResponse,
|
@Named("failure") EppResponseMessage failureResponse,
|
||||||
@Named("create") String createTemplate) {
|
@Named("create") String createTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"create",
|
||||||
failureResponse,
|
failureResponse,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -334,6 +344,7 @@ public class EppModule {
|
||||||
@Named("success") EppResponseMessage successResponse,
|
@Named("success") EppResponseMessage successResponse,
|
||||||
@Named("delete") String deleteTemplate) {
|
@Named("delete") String deleteTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"delete",
|
||||||
successResponse,
|
successResponse,
|
||||||
deleteTemplate,
|
deleteTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -349,6 +360,7 @@ public class EppModule {
|
||||||
@Named("failure") EppResponseMessage failureResponse,
|
@Named("failure") EppResponseMessage failureResponse,
|
||||||
@Named("delete") String deleteTemplate) {
|
@Named("delete") String deleteTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"delete",
|
||||||
failureResponse,
|
failureResponse,
|
||||||
deleteTemplate,
|
deleteTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -364,6 +376,7 @@ public class EppModule {
|
||||||
@Named("success") EppResponseMessage successResponse,
|
@Named("success") EppResponseMessage successResponse,
|
||||||
@Named("logout") String logoutTemplate) {
|
@Named("logout") String logoutTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"logout",
|
||||||
successResponse,
|
successResponse,
|
||||||
logoutTemplate,
|
logoutTemplate,
|
||||||
(clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid));
|
(clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid));
|
||||||
|
@ -376,6 +389,7 @@ public class EppModule {
|
||||||
@Named("domainExists") EppResponseMessage domainExistsResponse,
|
@Named("domainExists") EppResponseMessage domainExistsResponse,
|
||||||
@Named("check") String checkTemplate) {
|
@Named("check") String checkTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"check",
|
||||||
domainExistsResponse,
|
domainExistsResponse,
|
||||||
checkTemplate,
|
checkTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -391,6 +405,7 @@ public class EppModule {
|
||||||
@Named("domainNotExists") EppResponseMessage domainNotExistsResponse,
|
@Named("domainNotExists") EppResponseMessage domainNotExistsResponse,
|
||||||
@Named("check") String checkTemplate) {
|
@Named("check") String checkTemplate) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"check",
|
||||||
domainNotExistsResponse,
|
domainNotExistsResponse,
|
||||||
checkTemplate,
|
checkTemplate,
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
|
|
@ -25,8 +25,10 @@ import google.registry.monitoring.blackbox.handlers.SslClientInitializer;
|
||||||
import google.registry.monitoring.blackbox.handlers.WebWhoisActionHandler;
|
import google.registry.monitoring.blackbox.handlers.WebWhoisActionHandler;
|
||||||
import google.registry.monitoring.blackbox.handlers.WebWhoisMessageHandler;
|
import google.registry.monitoring.blackbox.handlers.WebWhoisMessageHandler;
|
||||||
import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
|
import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
|
||||||
|
import google.registry.monitoring.blackbox.metrics.MetricsCollector;
|
||||||
import google.registry.monitoring.blackbox.tokens.WebWhoisToken;
|
import google.registry.monitoring.blackbox.tokens.WebWhoisToken;
|
||||||
import google.registry.util.CircularList;
|
import google.registry.util.CircularList;
|
||||||
|
import google.registry.util.Clock;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
|
@ -172,9 +174,11 @@ public class WebWhoisModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@IntoSet
|
@IntoSet
|
||||||
ProbingSequence provideWebWhoisSequence(
|
ProbingSequence provideWebWhoisSequence(
|
||||||
@WebWhoisProtocol ProbingStep probingStep, WebWhoisToken webWhoisToken) {
|
@WebWhoisProtocol ProbingStep probingStep,
|
||||||
|
WebWhoisToken webWhoisToken,
|
||||||
return new ProbingSequence.Builder(webWhoisToken).add(probingStep).build();
|
MetricsCollector metrics,
|
||||||
|
Clock clock) {
|
||||||
|
return new ProbingSequence.Builder(webWhoisToken, metrics, clock).add(probingStep).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -16,9 +16,9 @@ package google.registry.monitoring.blackbox;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doCallRealMethod;
|
import static org.mockito.Mockito.doCallRealMethod;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.doThrow;
|
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
@ -27,10 +27,16 @@ import google.registry.monitoring.blackbox.connection.Protocol;
|
||||||
import google.registry.monitoring.blackbox.exceptions.FailureException;
|
import google.registry.monitoring.blackbox.exceptions.FailureException;
|
||||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||||
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
|
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
|
||||||
|
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
||||||
|
import google.registry.monitoring.blackbox.metrics.MetricsCollector;
|
||||||
|
import google.registry.monitoring.blackbox.metrics.MetricsCollector.ResponseType;
|
||||||
import google.registry.monitoring.blackbox.tokens.Token;
|
import google.registry.monitoring.blackbox.tokens.Token;
|
||||||
|
import google.registry.testing.FakeClock;
|
||||||
|
import google.registry.util.Clock;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.channel.embedded.EmbeddedChannel;
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import org.joda.time.Duration;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -53,6 +59,11 @@ import org.mockito.Mockito;
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class ProbingSequenceTest {
|
public class ProbingSequenceTest {
|
||||||
|
|
||||||
|
private static final String PROTOCOL_NAME = "PROTOCOL";
|
||||||
|
private static final String MESSAGE_NAME = "MESSAGE";
|
||||||
|
private static final String RESPONSE_NAME = "RESPONSE";
|
||||||
|
private static final Duration LATENCY = Duration.millis(2L);
|
||||||
|
|
||||||
/** Default mock {@link ProbingAction} returned when generating an action with a mockStep. */
|
/** Default mock {@link ProbingAction} returned when generating an action with a mockStep. */
|
||||||
private ProbingAction mockAction = Mockito.mock(ProbingAction.class);
|
private ProbingAction mockAction = Mockito.mock(ProbingAction.class);
|
||||||
|
|
||||||
|
@ -71,22 +82,39 @@ public class ProbingSequenceTest {
|
||||||
*/
|
*/
|
||||||
private Protocol mockProtocol = Mockito.mock(Protocol.class);
|
private Protocol mockProtocol = Mockito.mock(Protocol.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default mock {@link OutboundMessageType} returned by {@code mockStep} and occasionally other
|
||||||
|
* mock {@link ProbingStep}s.
|
||||||
|
*/
|
||||||
|
private OutboundMessageType mockMessage = Mockito.mock(OutboundMessageType.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link EmbeddedChannel} used to create new {@link ChannelPromise} objects returned by mock
|
* {@link EmbeddedChannel} used to create new {@link ChannelPromise} objects returned by mock
|
||||||
* {@link ProbingAction}s on their {@code call} methods.
|
* {@link ProbingAction}s on their {@code call} methods.
|
||||||
*/
|
*/
|
||||||
private EmbeddedChannel channel = new EmbeddedChannel();
|
private EmbeddedChannel channel = new EmbeddedChannel();
|
||||||
|
|
||||||
|
/** Default mock {@link MetricsCollector} passed into each {@link ProbingSequence} tested */
|
||||||
|
private MetricsCollector metrics = Mockito.mock(MetricsCollector.class);
|
||||||
|
|
||||||
|
/** Default mock {@link Clock} passed into each {@link ProbingSequence} tested */
|
||||||
|
private Clock clock = new FakeClock();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
// To avoid a NullPointerException, we must have a protocol return persistent connection as
|
// To avoid a NullPointerException, we must have a protocol return persistent connection as
|
||||||
// false.
|
// false.
|
||||||
doReturn(true).when(mockProtocol).persistentConnection();
|
doReturn(true).when(mockProtocol).persistentConnection();
|
||||||
|
doReturn(PROTOCOL_NAME).when(mockProtocol).name();
|
||||||
|
|
||||||
// In order to avoid a NullPointerException, we must have the protocol returned that stores
|
// In order to avoid a NullPointerException, we must have the protocol returned that stores
|
||||||
// persistent connection as false.
|
// persistent connection as false.
|
||||||
doReturn(mockProtocol).when(mockStep).protocol();
|
doReturn(mockProtocol).when(mockStep).protocol();
|
||||||
|
|
||||||
|
doReturn(MESSAGE_NAME).when(mockMessage).name();
|
||||||
|
doReturn(RESPONSE_NAME).when(mockMessage).responseName();
|
||||||
|
doReturn(mockMessage).when(mockStep).messageTemplate();
|
||||||
|
|
||||||
// Allows for test if channel is accurately set.
|
// Allows for test if channel is accurately set.
|
||||||
doCallRealMethod().when(mockToken).setChannel(any(Channel.class));
|
doCallRealMethod().when(mockToken).setChannel(any(Channel.class));
|
||||||
doCallRealMethod().when(mockToken).channel();
|
doCallRealMethod().when(mockToken).channel();
|
||||||
|
@ -102,7 +130,7 @@ public class ProbingSequenceTest {
|
||||||
ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
|
ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
ProbingSequence sequence =
|
ProbingSequence sequence =
|
||||||
new ProbingSequence.Builder(mockToken)
|
new ProbingSequence.Builder(mockToken, metrics, clock)
|
||||||
.add(firstStep)
|
.add(firstStep)
|
||||||
.add(secondStep)
|
.add(secondStep)
|
||||||
.add(thirdStep)
|
.add(thirdStep)
|
||||||
|
@ -127,7 +155,7 @@ public class ProbingSequenceTest {
|
||||||
ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
|
ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
ProbingSequence sequence =
|
ProbingSequence sequence =
|
||||||
new ProbingSequence.Builder(mockToken)
|
new ProbingSequence.Builder(mockToken, metrics, clock)
|
||||||
.add(thirdStep)
|
.add(thirdStep)
|
||||||
.add(secondStep)
|
.add(secondStep)
|
||||||
.markFirstRepeated()
|
.markFirstRepeated()
|
||||||
|
@ -148,8 +176,15 @@ public class ProbingSequenceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRunStep_Success() throws UndeterminedStateException {
|
public void testRunStep_Success() throws UndeterminedStateException {
|
||||||
// Always returns a succeeded future on call to mockAction.
|
// Always returns a succeeded future on call to mockAction. Also advances the FakeClock by
|
||||||
doReturn(channel.newSucceededFuture()).when(mockAction).call();
|
// standard LATENCY to check right latency is recorded.
|
||||||
|
doAnswer(
|
||||||
|
answer -> {
|
||||||
|
((FakeClock) clock).advanceBy(LATENCY);
|
||||||
|
return channel.newSucceededFuture();
|
||||||
|
})
|
||||||
|
.when(mockAction)
|
||||||
|
.call();
|
||||||
|
|
||||||
// Has mockStep always return mockAction on call to generateAction.
|
// Has mockStep always return mockAction on call to generateAction.
|
||||||
doReturn(mockAction).when(mockStep).generateAction(any(Token.class));
|
doReturn(mockAction).when(mockStep).generateAction(any(Token.class));
|
||||||
|
@ -165,7 +200,10 @@ public class ProbingSequenceTest {
|
||||||
|
|
||||||
// Build testable sequence from mocked components.
|
// Build testable sequence from mocked components.
|
||||||
ProbingSequence sequence =
|
ProbingSequence sequence =
|
||||||
new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build();
|
new ProbingSequence.Builder(mockToken, metrics, clock)
|
||||||
|
.add(mockStep)
|
||||||
|
.add(secondStep)
|
||||||
|
.build();
|
||||||
|
|
||||||
sequence.start();
|
sequence.start();
|
||||||
|
|
||||||
|
@ -182,12 +220,25 @@ public class ProbingSequenceTest {
|
||||||
|
|
||||||
// We should have modified the token's channel after the first, succeeded step.
|
// We should have modified the token's channel after the first, succeeded step.
|
||||||
assertThat(mockToken.channel()).isEqualTo(channel);
|
assertThat(mockToken.channel()).isEqualTo(channel);
|
||||||
|
|
||||||
|
// Verifies that metrics records the right kind of result (a success with the input protocol
|
||||||
|
// name and message name).
|
||||||
|
verify(metrics)
|
||||||
|
.recordResult(
|
||||||
|
PROTOCOL_NAME, MESSAGE_NAME, RESPONSE_NAME, ResponseType.SUCCESS, LATENCY.getMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRunLoop_Success() throws UndeterminedStateException {
|
public void testRunLoop_Success() throws UndeterminedStateException {
|
||||||
// Always returns a succeeded future on call to mockAction.
|
// Always returns a succeeded future on call to mockAction. Also advances the FakeClock by
|
||||||
doReturn(channel.newSucceededFuture()).when(mockAction).call();
|
// standard LATENCY to check right latency is recorded.
|
||||||
|
doAnswer(
|
||||||
|
answer -> {
|
||||||
|
((FakeClock) clock).advanceBy(LATENCY);
|
||||||
|
return channel.newSucceededFuture();
|
||||||
|
})
|
||||||
|
.when(mockAction)
|
||||||
|
.call();
|
||||||
|
|
||||||
// Has mockStep always return mockAction on call to generateAction
|
// Has mockStep always return mockAction on call to generateAction
|
||||||
doReturn(mockAction).when(mockStep).generateAction(mockToken);
|
doReturn(mockAction).when(mockStep).generateAction(mockToken);
|
||||||
|
@ -199,9 +250,21 @@ public class ProbingSequenceTest {
|
||||||
// Necessary for success of ProbingSequence runStep method as it calls get().protocol().
|
// Necessary for success of ProbingSequence runStep method as it calls get().protocol().
|
||||||
doReturn(mockProtocol).when(secondStep).protocol();
|
doReturn(mockProtocol).when(secondStep).protocol();
|
||||||
|
|
||||||
|
// Necessary for success of ProbingSequence recording metrics as it calls get()
|
||||||
|
// .messageTemplate.name().
|
||||||
|
doReturn(mockMessage).when(secondStep).messageTemplate();
|
||||||
|
|
||||||
// We ensure that secondStep has necessary attributes to be successful step to pass on to
|
// We ensure that secondStep has necessary attributes to be successful step to pass on to
|
||||||
// mockStep once more.
|
// mockStep once more. Also have clock time pass by standard LATENCY to ensure right latency
|
||||||
doReturn(channel.newSucceededFuture()).when(secondAction).call();
|
// is recorded.
|
||||||
|
doAnswer(
|
||||||
|
answer -> {
|
||||||
|
((FakeClock) clock).advanceBy(LATENCY);
|
||||||
|
return channel.newSucceededFuture();
|
||||||
|
})
|
||||||
|
.when(secondAction)
|
||||||
|
.call();
|
||||||
|
|
||||||
doReturn(secondAction).when(secondStep).generateAction(mockToken);
|
doReturn(secondAction).when(secondStep).generateAction(mockToken);
|
||||||
|
|
||||||
// We get a secondToken that is returned when we are on our second loop in the sequence. This
|
// We get a secondToken that is returned when we are on our second loop in the sequence. This
|
||||||
|
@ -217,12 +280,15 @@ public class ProbingSequenceTest {
|
||||||
|
|
||||||
// Build testable sequence from mocked components.
|
// Build testable sequence from mocked components.
|
||||||
ProbingSequence sequence =
|
ProbingSequence sequence =
|
||||||
new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build();
|
new ProbingSequence.Builder(mockToken, metrics, clock)
|
||||||
|
.add(mockStep)
|
||||||
|
.add(secondStep)
|
||||||
|
.build();
|
||||||
|
|
||||||
sequence.start();
|
sequence.start();
|
||||||
|
|
||||||
// We expect to have generated actions from mockStep twice (once for mockToken and once for
|
// We expect to have generated actions from mockStep twice (once for mockToken and once for
|
||||||
// secondToken), and we expectto have called each generated action only once, as when we move
|
// secondToken), and we expect to have called each generated action only once, as when we move
|
||||||
// on to mockStep the second time, it will terminate the sequence after calling thirdAction.
|
// on to mockStep the second time, it will terminate the sequence after calling thirdAction.
|
||||||
verify(mockStep).generateAction(mockToken);
|
verify(mockStep).generateAction(mockToken);
|
||||||
verify(mockStep).generateAction(secondToken);
|
verify(mockStep).generateAction(secondToken);
|
||||||
|
@ -236,6 +302,18 @@ public class ProbingSequenceTest {
|
||||||
|
|
||||||
// We should have modified the token's channel after the first, succeeded step.
|
// We should have modified the token's channel after the first, succeeded step.
|
||||||
assertThat(mockToken.channel()).isEqualTo(channel);
|
assertThat(mockToken.channel()).isEqualTo(channel);
|
||||||
|
|
||||||
|
// Verifies that metrics records the right kind of result (a success with the input protocol
|
||||||
|
// name and message name) two times: once for mockStep and once for secondStep.
|
||||||
|
verify(metrics, times(2))
|
||||||
|
.recordResult(
|
||||||
|
PROTOCOL_NAME, MESSAGE_NAME, RESPONSE_NAME, ResponseType.SUCCESS, LATENCY.getMillis());
|
||||||
|
|
||||||
|
// Verify that on second pass, since we purposely throw UnrecoverableStateException, we
|
||||||
|
// record the ERROR. Also, we haven't had any time pass in the fake clock, so recorded
|
||||||
|
// latency should be 0.
|
||||||
|
verify(metrics)
|
||||||
|
.recordResult(PROTOCOL_NAME, MESSAGE_NAME, RESPONSE_NAME, ResponseType.ERROR, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -250,14 +328,26 @@ public class ProbingSequenceTest {
|
||||||
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
// We create a second token that when used to generate an action throws an
|
// We create a second token that when used to generate an action throws an
|
||||||
// UnrecoverableStateException to terminate the sequence
|
// UnrecoverableStateException to terminate the sequence.
|
||||||
Token secondToken = Mockito.mock(Token.class);
|
Token secondToken = Mockito.mock(Token.class);
|
||||||
doReturn(secondToken).when(mockToken).next();
|
doReturn(secondToken).when(mockToken).next();
|
||||||
doThrow(new UnrecoverableStateException("")).when(mockStep).generateAction(secondToken);
|
|
||||||
|
// When we throw the UnrecoverableStateException, we ensure that the right latency is
|
||||||
|
// recorded by advancing the clock by LATENCY.
|
||||||
|
doAnswer(
|
||||||
|
answer -> {
|
||||||
|
((FakeClock) clock).advanceBy(LATENCY);
|
||||||
|
throw new UnrecoverableStateException("");
|
||||||
|
})
|
||||||
|
.when(mockStep)
|
||||||
|
.generateAction(secondToken);
|
||||||
|
|
||||||
// Build testable sequence from mocked components.
|
// Build testable sequence from mocked components.
|
||||||
ProbingSequence sequence =
|
ProbingSequence sequence =
|
||||||
new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build();
|
new ProbingSequence.Builder(mockToken, metrics, clock)
|
||||||
|
.add(mockStep)
|
||||||
|
.add(secondStep)
|
||||||
|
.build();
|
||||||
|
|
||||||
sequence.start();
|
sequence.start();
|
||||||
|
|
||||||
|
@ -278,8 +368,15 @@ public class ProbingSequenceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRunStep_FailureRunning() throws UndeterminedStateException {
|
public void testRunStep_FailureRunning() throws UndeterminedStateException {
|
||||||
// Returns a failed future when calling the generated mock action.
|
// Returns a failed future when calling the generated mock action. Also advances FakeClock by
|
||||||
doReturn(channel.newFailedFuture(new FailureException(""))).when(mockAction).call();
|
// LATENCY in order to check right latency is recorded.
|
||||||
|
doAnswer(
|
||||||
|
answer -> {
|
||||||
|
((FakeClock) clock).advanceBy(LATENCY);
|
||||||
|
return channel.newFailedFuture(new FailureException(""));
|
||||||
|
})
|
||||||
|
.when(mockAction)
|
||||||
|
.call();
|
||||||
|
|
||||||
// Returns mock action on call to generate action for ProbingStep.
|
// Returns mock action on call to generate action for ProbingStep.
|
||||||
doReturn(mockAction).when(mockStep).generateAction(mockToken);
|
doReturn(mockAction).when(mockStep).generateAction(mockToken);
|
||||||
|
@ -290,16 +387,42 @@ public class ProbingSequenceTest {
|
||||||
// We only expect to have called this action once, as we only get it from one generateAction
|
// We only expect to have called this action once, as we only get it from one generateAction
|
||||||
// call.
|
// call.
|
||||||
verify(mockAction).call();
|
verify(mockAction).call();
|
||||||
|
|
||||||
|
// Verifies that metrics records the right kind of result (a failure with the input protocol
|
||||||
|
// name and message name).
|
||||||
|
verify(metrics)
|
||||||
|
.recordResult(
|
||||||
|
PROTOCOL_NAME, MESSAGE_NAME, RESPONSE_NAME, ResponseType.FAILURE, LATENCY.getMillis());
|
||||||
|
|
||||||
|
// Verify that on second pass, since we purposely throw UnrecoverableStateException, we
|
||||||
|
// record the ERROR. We also should make sure LATENCY seconds have passed.
|
||||||
|
verify(metrics)
|
||||||
|
.recordResult(
|
||||||
|
PROTOCOL_NAME, MESSAGE_NAME, RESPONSE_NAME, ResponseType.ERROR, LATENCY.getMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRunStep_FailureGenerating() throws UndeterminedStateException {
|
public void testRunStep_FailureGenerating() throws UndeterminedStateException {
|
||||||
// Create a mock first step that returns the dummy action when called to generate an action.
|
// Mock first step throws an error when generating the first action, and advances the clock
|
||||||
doThrow(UndeterminedStateException.class).when(mockStep).generateAction(mockToken);
|
// by LATENCY.
|
||||||
|
doAnswer(
|
||||||
|
answer -> {
|
||||||
|
((FakeClock) clock).advanceBy(LATENCY);
|
||||||
|
throw new UndeterminedStateException("");
|
||||||
|
})
|
||||||
|
.when(mockStep)
|
||||||
|
.generateAction(mockToken);
|
||||||
|
|
||||||
|
// Tests generic behavior we expect when we fail in generating or calling an action.
|
||||||
testActionFailure();
|
testActionFailure();
|
||||||
|
|
||||||
// We expect to have never called this action, as we fail each time whenever generating actions.
|
// We expect to have never called this action, as we fail each time whenever generating actions.
|
||||||
verify(mockAction, times(0)).call();
|
verify(mockAction, times(0)).call();
|
||||||
|
|
||||||
|
// Verify that we record two errors, first for being unable to generate the action, second
|
||||||
|
// for terminating the sequence.
|
||||||
|
verify(metrics, times(2))
|
||||||
|
.recordResult(
|
||||||
|
PROTOCOL_NAME, MESSAGE_NAME, RESPONSE_NAME, ResponseType.ERROR, LATENCY.getMillis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,4 +38,14 @@ public class TestMessage implements OutboundMessageType, InboundMessageType {
|
||||||
message = args[0];
|
message = args[0];
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String responseName() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2019 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.monitoring.blackbox.metrics;
|
||||||
|
|
||||||
|
import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.assertThat;
|
||||||
|
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import google.registry.monitoring.blackbox.metrics.MetricsCollector.ResponseType;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link MetricsCollector}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class MetricsCollectorTest {
|
||||||
|
|
||||||
|
private final String requestName = "request";
|
||||||
|
private final String responseName = "response";
|
||||||
|
private final String protocol = "protocol";
|
||||||
|
|
||||||
|
private final MetricsCollector metrics = new MetricsCollector();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
metrics.resetMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOneRecord() {
|
||||||
|
metrics.recordResult(protocol, requestName, responseName, ResponseType.SUCCESS, 100);
|
||||||
|
|
||||||
|
assertThat(MetricsCollector.responsesCounter)
|
||||||
|
.hasValueForLabels(1, protocol, requestName, responseName, ResponseType.SUCCESS.name())
|
||||||
|
.and()
|
||||||
|
.hasNoOtherValues();
|
||||||
|
|
||||||
|
assertThat(MetricsCollector.latencyMs)
|
||||||
|
.hasDataSetForLabels(
|
||||||
|
ImmutableSet.of(100), protocol, requestName, responseName, ResponseType.SUCCESS.name())
|
||||||
|
.and()
|
||||||
|
.hasNoOtherValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleRecords_sameStatus() {
|
||||||
|
metrics.recordResult(protocol, requestName, responseName, ResponseType.FAILURE, 100);
|
||||||
|
metrics.recordResult(protocol, requestName, responseName, ResponseType.FAILURE, 200);
|
||||||
|
|
||||||
|
assertThat(MetricsCollector.responsesCounter)
|
||||||
|
.hasValueForLabels(2, protocol, requestName, responseName, ResponseType.FAILURE.name())
|
||||||
|
.and()
|
||||||
|
.hasNoOtherValues();
|
||||||
|
|
||||||
|
assertThat(MetricsCollector.latencyMs)
|
||||||
|
.hasDataSetForLabels(
|
||||||
|
ImmutableSet.of(100, 200),
|
||||||
|
protocol,
|
||||||
|
requestName,
|
||||||
|
responseName,
|
||||||
|
ResponseType.FAILURE.name())
|
||||||
|
.and()
|
||||||
|
.hasNoOtherValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleRecords_differentStatus() {
|
||||||
|
metrics.recordResult(protocol, requestName, responseName, ResponseType.SUCCESS, 100);
|
||||||
|
metrics.recordResult(protocol, requestName, responseName, ResponseType.FAILURE, 200);
|
||||||
|
|
||||||
|
assertThat(MetricsCollector.responsesCounter)
|
||||||
|
.hasValueForLabels(1, protocol, requestName, responseName, ResponseType.SUCCESS.name())
|
||||||
|
.and()
|
||||||
|
.hasValueForLabels(1, protocol, requestName, responseName, ResponseType.FAILURE.name())
|
||||||
|
.and()
|
||||||
|
.hasNoOtherValues();
|
||||||
|
|
||||||
|
assertThat(MetricsCollector.latencyMs)
|
||||||
|
.hasDataSetForLabels(
|
||||||
|
ImmutableSet.of(100), protocol, requestName, responseName, ResponseType.SUCCESS.name())
|
||||||
|
.and()
|
||||||
|
.hasDataSetForLabels(
|
||||||
|
ImmutableSet.of(200), protocol, requestName, responseName, ResponseType.FAILURE.name())
|
||||||
|
.and()
|
||||||
|
.hasNoOtherValues();
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,13 +92,14 @@ public class EppUtils {
|
||||||
|
|
||||||
/** Returns standard hello request with supplied response. */
|
/** Returns standard hello request with supplied response. */
|
||||||
public static EppRequestMessage getHelloMessage(EppResponseMessage greetingResponse) {
|
public static EppRequestMessage getHelloMessage(EppResponseMessage greetingResponse) {
|
||||||
return new EppRequestMessage(greetingResponse, null, (a, b) -> ImmutableMap.of());
|
return new EppRequestMessage("hello", greetingResponse, null, (a, b) -> ImmutableMap.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns standard login request with supplied userId, userPassword, and response. */
|
/** Returns standard login request with supplied userId, userPassword, and response. */
|
||||||
public static EppRequestMessage getLoginMessage(
|
public static EppRequestMessage getLoginMessage(
|
||||||
EppResponseMessage response, String userId, String userPassword) {
|
EppResponseMessage response, String userId, String userPassword) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"login",
|
||||||
response,
|
response,
|
||||||
"login.xml",
|
"login.xml",
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -111,6 +112,7 @@ public class EppUtils {
|
||||||
/** Returns standard create request with supplied response. */
|
/** Returns standard create request with supplied response. */
|
||||||
public static EppRequestMessage getCreateMessage(EppResponseMessage response) {
|
public static EppRequestMessage getCreateMessage(EppResponseMessage response) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"create",
|
||||||
response,
|
response,
|
||||||
"create.xml",
|
"create.xml",
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -122,6 +124,7 @@ public class EppUtils {
|
||||||
/** Returns standard delete request with supplied response. */
|
/** Returns standard delete request with supplied response. */
|
||||||
public static EppRequestMessage getDeleteMessage(EppResponseMessage response) {
|
public static EppRequestMessage getDeleteMessage(EppResponseMessage response) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"delete",
|
||||||
response,
|
response,
|
||||||
"delete.xml",
|
"delete.xml",
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
@ -133,6 +136,7 @@ public class EppUtils {
|
||||||
/** Returns standard logout request with supplied response. */
|
/** Returns standard logout request with supplied response. */
|
||||||
public static EppRequestMessage getLogoutMessage(EppResponseMessage successResponse) {
|
public static EppRequestMessage getLogoutMessage(EppResponseMessage successResponse) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"logout",
|
||||||
successResponse,
|
successResponse,
|
||||||
"logout.xml",
|
"logout.xml",
|
||||||
(clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid));
|
(clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid));
|
||||||
|
@ -141,6 +145,7 @@ public class EppUtils {
|
||||||
/** Returns standard check request with supplied response. */
|
/** Returns standard check request with supplied response. */
|
||||||
public static EppRequestMessage getCheckMessage(EppResponseMessage response) {
|
public static EppRequestMessage getCheckMessage(EppResponseMessage response) {
|
||||||
return new EppRequestMessage(
|
return new EppRequestMessage(
|
||||||
|
"check",
|
||||||
response,
|
response,
|
||||||
"check.xml",
|
"check.xml",
|
||||||
(clTrid, domain) ->
|
(clTrid, domain) ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue