mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
Prober circular list (#218)
* Fixed changes suggested by CydeWeys * Fixed Javadoc and comments for ActionHandler * Fixed comments and JavaDoc on other files * EOL added * Removed Unnecessary Files * fixed .gradle files styles * Removed outbound message from ActionHandler's fields and renamed Marker Interfaces * Fixed javadoc for Marker Interfaced * Modified Comments on ActionHandler * Removed LocalAddress from Protocol * Fixed Travis Build Issues * Rebased to Master and added in modified Handlers and ProbingAction * Added missing license headers and JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor Style Fix * Full WebWhoIs Sequence Added * fixed build issues * Refactored by responses suggested by jianglai. * Minor Style Fixes * Updated build.gradle file * Modified license header dates * Updated WebWhois tests. * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * SpotlessApply run to fix style issues * Added license header and newline where appropriate. * Javadoc style fix in tests and removed unused methods * Refactored ProbingAction to minimize number of unnecessary methods * Initial Commit. * Initial Commit. * Deleted unfinished features. Added ActionHandler and its Unit Tests. * Included prober subproject in settings.gradle * Added Protocol Class and its Basic Unit Tests * Added Protocol Class and its Basic Unit Tests * Added Changes Suggested by jianglai * Fixed Gitignore to take out AutoValue generated code * Fixed Gitignore to take out AutoValue generated code * Removed AutoValue java files * Added gitignore within prober * Removed all generated java * Final Changes in .gitignore * Final Changes in .gitignore * Added Ssl and WebWhois Action Handlers and their unit tests in addition to the ProbingAction class * Fixed build.gradle changes requested * Removed Files irrelevant to current pull request * Fixed changes suggested by CydeWeys * Fixed changes suggested by CydeWeys * Fixed changes suggested by CydeWeys * Fixed changes suggested by CydeWeys * Minor fixes to ActionHandler, as responded in comments, removed package-info, and updated settings.gradle * Minor fixes to ActionHandler, as responded in comments, removed package-info, and updated settings.gradle * Fully Updated ActionHandler (missing updated JavaDoc) * Added changed Protocol and both Inbound and Outbound Markers * Removed AutoVaue ignore clause from .gitignore * Removed AutoVaue ignore clause from .gitignore * removed unneccessary dependencies in build.gradle * Fixed Javadoc and comments for ActionHandler * Fixed comments and JavaDoc on other files * EOL added * Removed Unnecessary Files * fixed .gradle files styles * Removed outbound message from ActionHandler's fields and renamed Marker Interfaces * Fixed javadoc for Marker Interfaced * Modified Comments on ActionHandler * Removed LocalAddress from Protocol * Fixed Travis Build Issues * Rebased to Master and added in modified Handlers and ProbingAction * Rebased to Master and added in modified Handlers and ProbingAction * Rebased to Master and added in modified Handlers and ProbingAction * Rebased to Master and added in modified Handlers and ProbingAction * Rebased to Master and added in modified Handlers and ProbingAction * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor Style Fix * Minor Style Fix * Minor Style Fix * Minor Style Fix * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * fixed build issues * fixed build issues * fixed build issues * fixed build issues * fixed build issues * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Minor Style Fixes * Minor Style Fixes * Minor Style Fixes * Minor Style Fixes * Minor Style Fixes * Updated build.gradle file * Updated build.gradle file * Updated build.gradle file * Updated build.gradle file * Modified license header dates * Modified license header dates * Modified license header dates * Modified license header dates * Modified license header dates * Updated WebWhois tests. * Updated WebWhois tests. * Updated WebWhois tests. * Updated WebWhois tests. * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * SpotlessApply run to fix style issues * SpotlessApply run to fix style issues * SpotlessApply run to fix style issues * SpotlessApply run to fix style issues * SpotlessApply run to fix style issues * Added license header and newline where appropriate. * Added license header and newline where appropriate. * Added license header and newline where appropriate. * Added license header and newline where appropriate. * Added license header and newline where appropriate. * Javadoc style fix in tests and removed unused methods * Javadoc style fix in tests and removed unused methods * Javadoc style fix in tests and removed unused methods * Javadoc style fix in tests and removed unused methods * Javadoc style fix in tests and removed unused methods * Refactored ProbingAction to minimize number of unnecessary methods * Refactored ProbingAction to minimize number of unnecessary methods * Refactored ProbingAction to minimize number of unnecessary methods * Refactored ProbingAction to minimize number of unnecessary methods * Modified tests for WebWhois according to changes suggested by laijiang. * Modified tests for WebWhois according to changes suggested by laijiang. * Modified tests for WebWhois according to changes suggested by laijiang. * Modified tests for WebWhois according to changes suggested by laijiang. * Modified tests for WebWhois according to changes suggested by laijiang. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Rebased to master * Rebased to master * Updated issues in rebasing * Updated issues in rebasing * Minor style change on prober/build.gradle * Minor style change on prober/build.gradle * Fixed warnings for java compilation * Fixed warnings for java compilation * Fixed files to pass all style tests * Fixed files to pass all style tests * Fixed files to pass all style tests * Minor syle fixes after succesful rebase onto master * Fixed changes suggested by CydeWeys * Fixed changes suggested by CydeWeys * Fixed changes suggested by CydeWeys * Rebased to Master and added in modified Handlers and ProbingAction * Rebased to Master and added in modified Handlers and ProbingAction * Rebased to Master and added in modified Handlers and ProbingAction * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor Style Fix * Minor Style Fix * Minor Style Fix * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * fixed build issues * fixed build issues * fixed build issues * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Refactored by responses suggested by jianglai. * Minor Style Fixes * Minor Style Fixes * Updated build.gradle file * Updated build.gradle file * Updated build.gradle file * Modified license header dates * Modified license header dates * Modified license header dates * Updated WebWhois tests. * Updated WebWhois tests. * Updated WebWhois tests. * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * Refactored WebWhois to accomodate jianglai's suggested changes and modified tests to reflect this refactoring * SpotlessApply run to fix style issues * Added circular linked list to utils * Added circular linked list to utils * Added circular linked list to utils * Added circular linked list to utils * Added circular linked list to utils * License Header added * License Header added * License Header added * License Header added * License Header added * Added license header and newline where appropriate. * Added license header and newline where appropriate. * Refactored probing sequence to be circular linked list iterator * Refactored probing sequence to be circular linked list iterator * Refactored probing sequence to be circular linked list iterator * Refactored probing sequence to be circular linked list iterator * Javadoc style fix in tests and removed unused methods * Javadoc style fix in tests and removed unused methods * Javadoc style fix in tests and removed unused methods * Modified ProbingStep tests to reflect new ProbingStep structure. * Modified ProbingStep tests to reflect new ProbingStep structure. * Refactored ProbingAction to minimize number of unnecessary methods * Refactored ProbingAction to minimize number of unnecessary methods * Refactored ProbingAction to minimize number of unnecessary methods * Modified tests for WebWhois according to changes suggested by laijiang. * Modified tests for WebWhois according to changes suggested by laijiang. * Modified tests for WebWhois according to changes suggested by laijiang. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Rebased to master * ProbingStepTest modified to have fewer unnecessary helper methods * ProbingStepTest modified to have fewer unnecessary helper methods * Updated issues in rebasing * Updated issues in rebasing * Added missing license header to DefaultCircularLinkedListIterator * Fixed max column length to be 100 * Fixed max column length to be 100 * Minor changes to pass style tests * Successful rebase onto finished web-whois branch * Removed need for TestTokens with Mockito mocks of Tokens * Fixed style issues in DefaultCircularLinkedListIterator and AbstractCircularLinkedListIterator * Modified CircularList according to changes suggested by jianglai. * Merge branch 'master' into prober-circular-list * Modified ProbingSequenceTest to not expect unnecessary NullPointerException * ProbingSequence and tests modified to reflect addition of UnrecoverableStateException and restarts on failures * Modified ProbingSequence and its tests to reflect action generation and calling being put in the same try catch block
This commit is contained in:
parent
5b2ce06b79
commit
02c2b37fe3
10 changed files with 697 additions and 281 deletions
|
@ -135,12 +135,12 @@ public abstract class ProbingAction implements Callable<ChannelFuture> {
|
||||||
* when the connection future is successful.</p>
|
* when the connection future is successful.</p>
|
||||||
*
|
*
|
||||||
* <p>Once the connection is successful, we establish which of the handlers in the pipeline is
|
* <p>Once the connection is successful, we establish which of the handlers in the pipeline is
|
||||||
* the {@link ActionHandler}.From that, we can obtain a future that is marked as a success when
|
* the {@link ActionHandler}.From that, we can obtain a future that is marked as a success when we
|
||||||
* we receive an expected response from the server.</p>
|
* receive an expected response from the server.</p>
|
||||||
*
|
*
|
||||||
* <p>Next, we set a timer set to a specified delay. After the delay has passed, we send the
|
* <p>Next, we set a timer set to a specified delay. After the delay has passed, we send the
|
||||||
* {@code outboundMessage} down the channel pipeline, and when we observe a success or failure,
|
* {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we
|
||||||
* we inform the {@link ProbingStep} of this.</p>
|
* inform the {@link ProbingStep} of this.</p>
|
||||||
*
|
*
|
||||||
* @return {@link ChannelFuture} that denotes when the action has been successfully performed.
|
* @return {@link ChannelFuture} that denotes when the action has been successfully performed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,14 +14,25 @@
|
||||||
|
|
||||||
package google.registry.monitoring.blackbox;
|
package google.registry.monitoring.blackbox;
|
||||||
|
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
|
||||||
import google.registry.monitoring.blackbox.tokens.Token;
|
import google.registry.monitoring.blackbox.tokens.Token;
|
||||||
|
import google.registry.util.CircularList;
|
||||||
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.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents Sequence of {@link ProbingStep}s that the Prober performs in order.
|
* Represents Sequence of {@link ProbingStep}s that the Prober performs in order.
|
||||||
*
|
*
|
||||||
|
* <p>Inherits from {@link CircularList}, with element type of
|
||||||
|
* {@link ProbingStep} as the manner in which the sequence is carried out is similar to the {@link
|
||||||
|
* CircularList}. However, the {@link Builder} of {@link ProbingSequence} override
|
||||||
|
* {@link CircularList.AbstractBuilder} allowing for more customized flows, that are looped, but
|
||||||
|
* not necessarily entirely circular.
|
||||||
|
* Example: first -> second -> third -> fourth -> second -> third -> fourth -> second -> ...
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>Created with {@link Builder} where we specify {@link EventLoopGroup}, {@link AbstractChannel}
|
* <p>Created with {@link Builder} where we specify {@link EventLoopGroup}, {@link AbstractChannel}
|
||||||
* class type, then sequentially add in the {@link ProbingStep.Builder}s in order and mark which one
|
* class type, then sequentially add in the {@link ProbingStep.Builder}s in order and mark which one
|
||||||
|
@ -31,78 +42,202 @@ import io.netty.channel.EventLoopGroup;
|
||||||
* the first one is activated with the requisite {@link Token}, the {@link ProbingStep}s do the rest
|
* the first one is activated with the requisite {@link Token}, the {@link ProbingStep}s do the rest
|
||||||
* of the work.</p>
|
* of the work.</p>
|
||||||
*/
|
*/
|
||||||
public class ProbingSequence {
|
public class ProbingSequence extends CircularList<ProbingStep> {
|
||||||
|
|
||||||
private ProbingStep firstStep;
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
|
|
||||||
private ProbingSequence(ProbingStep firstStep, Token startToken) {
|
/**
|
||||||
this.firstStep = firstStep;
|
* Each {@link ProbingSequence} is considered to not be the last step unless specified by the
|
||||||
|
* {@link Builder}.
|
||||||
|
*/
|
||||||
|
private boolean lastStep = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ProbingSequence} object that represents first step in the sequence.
|
||||||
|
*/
|
||||||
|
private ProbingSequence first;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard constructor for {@link ProbingSequence} in the list that assigns value and token.
|
||||||
|
*/
|
||||||
|
private ProbingSequence(ProbingStep value, Token startToken) {
|
||||||
|
super(value);
|
||||||
this.startToken = startToken;
|
this.startToken = startToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used in {@link Builder} to mark the last step in the sequence.
|
||||||
|
*/
|
||||||
|
private void markLast() {
|
||||||
|
lastStep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains next {@link ProbingSequence} in sequence instead of next {@link CircularList}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ProbingSequence next() {
|
||||||
|
return (ProbingSequence) super.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts ProbingSequence by calling first {@code runStep} with {@code startToken}.
|
||||||
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
// calls the first step with startToken;
|
runStep(startToken);
|
||||||
firstStep.accept(startToken);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates new {@link ProbingAction} from {@link ProbingStep}, calls the action, then retrieves
|
||||||
|
* the result of the action.
|
||||||
|
*
|
||||||
|
* @param token - used to generate the {@link ProbingAction} by calling {@code
|
||||||
|
* get().generateAction}.
|
||||||
|
*
|
||||||
|
* <p>Calls {@code runNextStep} to have next {@link ProbingSequence} call {@code runStep}
|
||||||
|
* with next token depending on if the current step is the last one in the sequence.
|
||||||
|
*
|
||||||
|
* <p>If unable to generate the action, or the calling the action results in an immediate error,
|
||||||
|
* we note an error. Otherwise, if the future marked as finished when the action is completed is
|
||||||
|
* marked as a success, we note a success. Otherwise, if the cause of failure will either be a
|
||||||
|
* failure or error. </p>
|
||||||
|
*/
|
||||||
|
private void runStep(Token token) {
|
||||||
|
ProbingAction currentAction;
|
||||||
|
ChannelFuture future;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt to generate new action. On error, move on to next step.
|
||||||
|
currentAction = get().generateAction(token);
|
||||||
|
|
||||||
|
// Call the generated action.
|
||||||
|
future = currentAction.call();
|
||||||
|
|
||||||
|
} catch (UnrecoverableStateException e) {
|
||||||
|
// On an UnrecoverableStateException, terminate the sequence.
|
||||||
|
logger.atSevere().withCause(e).log(
|
||||||
|
"Unrecoverable error in generating or calling action.");
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
// Restart the sequence at the very first step.
|
||||||
|
restartSequence();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
future.addListener(f -> {
|
||||||
|
if (f.isSuccess()) {
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
// If not unrecoverable, we restart the sequence.
|
||||||
|
if (!(f.cause() instanceof UnrecoverableStateException)) {
|
||||||
|
restartSequence();
|
||||||
|
}
|
||||||
|
// Otherwise, we just terminate the full sequence.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get().protocol().persistentConnection()) {
|
||||||
|
// If the connection is persistent, we store the channel in the token.
|
||||||
|
token.setChannel(currentAction.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Calls next runStep
|
||||||
|
runNextStep(token);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to first generate the next token, then call runStep on the next {@link
|
||||||
|
* ProbingSequence}.
|
||||||
|
*/
|
||||||
|
private void runNextStep(Token token) {
|
||||||
|
token = lastStep ? token.next() : token;
|
||||||
|
next().runStep(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to restart the sequence at the very first step, with a channel-less {@link
|
||||||
|
* Token}.
|
||||||
|
*/
|
||||||
|
private void restartSequence() {
|
||||||
|
// Gets next possible token to insure no replicated domains used.
|
||||||
|
Token restartToken = startToken.next();
|
||||||
|
|
||||||
|
// Makes sure channel from original token isn't passed down.
|
||||||
|
restartToken.setChannel(null);
|
||||||
|
|
||||||
|
// Runs the very first step with starting token.
|
||||||
|
first.runStep(restartToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns {@link ProbingStep.Builder}s into fully self-dependent sequence with supplied {@link
|
* Turns {@link ProbingStep.Builder}s into fully self-dependent sequence with supplied {@link
|
||||||
* Bootstrap}.
|
* Bootstrap}.
|
||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder extends CircularList.AbstractBuilder<ProbingStep, ProbingSequence> {
|
||||||
|
|
||||||
private ProbingStep currentStep;
|
private ProbingSequence firstRepeatedSequenceStep;
|
||||||
private ProbingStep firstStep;
|
|
||||||
private ProbingStep firstRepeatedStep;
|
|
||||||
|
|
||||||
private Token startToken;
|
private Token startToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Builder must also be supplied with a {@link Token} to construct a {@link
|
||||||
|
* ProbingSequence}.
|
||||||
|
*/
|
||||||
public Builder(Token startToken) {
|
public Builder(Token startToken) {
|
||||||
this.startToken = startToken;
|
this.startToken = startToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds {@link ProbingStep}, which is supplied with {@link Bootstrap}, built, and pointed to by
|
|
||||||
* the previous {@link ProbingStep} added.
|
|
||||||
*/
|
|
||||||
public Builder addStep(ProbingStep step) {
|
|
||||||
|
|
||||||
if (currentStep == null) {
|
|
||||||
firstStep = step;
|
|
||||||
} else {
|
|
||||||
currentStep.nextStep(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentStep = step;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We take special note of the first repeated step.
|
* We take special note of the first repeated step.
|
||||||
*/
|
*/
|
||||||
public Builder markFirstRepeated() {
|
public Builder markFirstRepeated() {
|
||||||
firstRepeatedStep = currentStep;
|
firstRepeatedSequenceStep = current;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder add(ProbingStep value) {
|
||||||
|
super.add(value);
|
||||||
|
current.first = first;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProbingSequence create(ProbingStep value) {
|
||||||
|
return new ProbingSequence(value, startToken);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Points last {@link ProbingStep} to the {@code firstRepeatedStep} and calls private
|
* Points last {@link ProbingStep} to the {@code firstRepeatedSequenceStep} and calls private
|
||||||
* constructor to create {@link ProbingSequence}.
|
* constructor to create {@link ProbingSequence}.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public ProbingSequence build() {
|
public ProbingSequence build() {
|
||||||
if (firstRepeatedStep == null) {
|
if (firstRepeatedSequenceStep == null) {
|
||||||
firstRepeatedStep = firstStep;
|
firstRepeatedSequenceStep = first;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentStep.nextStep(firstRepeatedStep);
|
current.markLast();
|
||||||
currentStep.lastStep();
|
current.setNext(firstRepeatedSequenceStep);
|
||||||
return new ProbingSequence(this.firstStep, this.startToken);
|
return first;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,10 @@
|
||||||
package google.registry.monitoring.blackbox;
|
package google.registry.monitoring.blackbox;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import com.google.common.flogger.FluentLogger;
|
|
||||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||||
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
||||||
import google.registry.monitoring.blackbox.tokens.Token;
|
import google.registry.monitoring.blackbox.tokens.Token;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,15 +31,7 @@ import org.joda.time.Duration;
|
||||||
* Token} and from that, generates a new {@link ProbingAction} to call.</p>
|
* Token} and from that, generates a new {@link ProbingAction} to call.</p>
|
||||||
*/
|
*/
|
||||||
@AutoValue
|
@AutoValue
|
||||||
public abstract class ProbingStep implements Consumer<Token> {
|
public abstract class ProbingStep {
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Necessary boolean to inform when to obtain next {@link Token}
|
|
||||||
*/
|
|
||||||
protected boolean isLastStep = false;
|
|
||||||
private ProbingStep nextStep;
|
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new AutoValue_ProbingStep.Builder();
|
return new AutoValue_ProbingStep.Builder();
|
||||||
|
@ -69,22 +58,10 @@ public abstract class ProbingStep implements Consumer<Token> {
|
||||||
*/
|
*/
|
||||||
abstract Bootstrap bootstrap();
|
abstract Bootstrap bootstrap();
|
||||||
|
|
||||||
void lastStep() {
|
|
||||||
isLastStep = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void nextStep(ProbingStep step) {
|
|
||||||
this.nextStep = step;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProbingStep nextStep() {
|
|
||||||
return this.nextStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new {@link ProbingAction} from {@code token} modified {@link OutboundMessageType}
|
* Generates a new {@link ProbingAction} from {@code token} modified {@link OutboundMessageType}
|
||||||
*/
|
*/
|
||||||
private ProbingAction generateAction(Token token) throws UndeterminedStateException {
|
public ProbingAction generateAction(Token token) throws UndeterminedStateException {
|
||||||
OutboundMessageType message = token.modifyMessage(messageTemplate());
|
OutboundMessageType message = token.modifyMessage(messageTemplate());
|
||||||
ProbingAction.Builder probingActionBuilder = ProbingAction.builder()
|
ProbingAction.Builder probingActionBuilder = ProbingAction.builder()
|
||||||
.setDelay(duration())
|
.setDelay(duration())
|
||||||
|
@ -101,71 +78,6 @@ public abstract class ProbingStep implements Consumer<Token> {
|
||||||
return probingActionBuilder.build();
|
return probingActionBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* On the last step, gets the next {@link Token}. Otherwise, uses the same one.
|
|
||||||
*/
|
|
||||||
private Token generateNextToken(Token token) {
|
|
||||||
return isLastStep ? token.next() : token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates new {@link ProbingAction}, calls the action, then retrieves the result of the
|
|
||||||
* action.
|
|
||||||
*
|
|
||||||
* @param token - used to generate the {@link ProbingAction} by calling {@code generateAction}.
|
|
||||||
*
|
|
||||||
* <p>If unable to generate the action, or the calling the action results in an immediate error,
|
|
||||||
* we note an error. Otherwise, if the future marked as finished when the action is completed is
|
|
||||||
* marked as a success, we note a success. Otherwise, if the cause of failure will either be a
|
|
||||||
* failure or error. </p>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void accept(Token token) {
|
|
||||||
ProbingAction currentAction;
|
|
||||||
//attempt to generate new action. On error, move on to next step
|
|
||||||
try {
|
|
||||||
currentAction = generateAction(token);
|
|
||||||
} catch (UndeterminedStateException e) {
|
|
||||||
logger.atWarning().withCause(e).log("Error in Action Generation");
|
|
||||||
nextStep.accept(generateNextToken(token));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelFuture future;
|
|
||||||
try {
|
|
||||||
//call the generated action
|
|
||||||
future = currentAction.call();
|
|
||||||
} catch (Exception e) {
|
|
||||||
//On error in calling action, log error and note an error
|
|
||||||
logger.atWarning().withCause(e).log("Error in Action Performed");
|
|
||||||
|
|
||||||
//Move on to next step in ProbingSequence
|
|
||||||
nextStep.accept(generateNextToken(token));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
future.addListener(f -> {
|
|
||||||
if (f.isSuccess()) {
|
|
||||||
//On a successful result, we log as a successful step, and not a success
|
|
||||||
logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protocol().persistentConnection()) {
|
|
||||||
//If the connection is persistent, we store the channel in the token
|
|
||||||
token.setChannel(currentAction.channel());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Move on the the next step in the ProbingSequence
|
|
||||||
nextStep.accept(generateNextToken(token));
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
return String.format("ProbingStep with Protocol: %s\n"
|
return String.format("ProbingStep with Protocol: %s\n"
|
||||||
|
@ -175,7 +87,7 @@ public abstract class ProbingStep implements Consumer<Token> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default {@link AutoValue.Builder} for {@link ProbingStep}.
|
* Standard {@link AutoValue.Builder} for {@link ProbingStep}.
|
||||||
*/
|
*/
|
||||||
@AutoValue.Builder
|
@AutoValue.Builder
|
||||||
public abstract static class Builder {
|
public abstract static class Builder {
|
||||||
|
@ -190,7 +102,5 @@ public abstract class ProbingStep implements Consumer<Token> {
|
||||||
|
|
||||||
public abstract ProbingStep build();
|
public abstract ProbingStep build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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.tokens.WebWhoisToken;
|
import google.registry.monitoring.blackbox.tokens.WebWhoisToken;
|
||||||
|
import google.registry.util.CircularList;
|
||||||
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;
|
||||||
|
@ -189,7 +190,7 @@ public class WebWhoisModule {
|
||||||
WebWhoisToken webWhoisToken) {
|
WebWhoisToken webWhoisToken) {
|
||||||
|
|
||||||
return new ProbingSequence.Builder(webWhoisToken)
|
return new ProbingSequence.Builder(webWhoisToken)
|
||||||
.addStep(probingStep)
|
.add(probingStep)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +206,10 @@ public class WebWhoisModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
@WebWhoisProtocol
|
@WebWhoisProtocol
|
||||||
ImmutableList<String> provideTopLevelDomains() {
|
CircularList<String> provideTopLevelDomains() {
|
||||||
return ImmutableList.of("how", "soy", "xn--q9jyb4c");
|
return new CircularList.Builder<String>()
|
||||||
|
.add("how", "soy", "xn--q9jyb4c")
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -244,6 +247,5 @@ public class WebWhoisModule {
|
||||||
public @interface WebWhoisProtocol {
|
public @interface WebWhoisProtocol {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when error is severe enough that sequence cannot recover, and should be
|
||||||
|
* terminated as a result.
|
||||||
|
*/
|
||||||
|
public class UnrecoverableStateException extends UndeterminedStateException {
|
||||||
|
|
||||||
|
public UnrecoverableStateException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnrecoverableStateException(Throwable e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableList;
|
||||||
import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol;
|
import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol;
|
||||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||||
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
||||||
import java.util.Iterator;
|
import google.registry.util.CircularList;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,26 +38,20 @@ public class WebWhoisToken extends Token {
|
||||||
/**
|
/**
|
||||||
* {@link ImmutableList} of all top level domains to be probed.
|
* {@link ImmutableList} of all top level domains to be probed.
|
||||||
*/
|
*/
|
||||||
private final Iterator<String> topLevelDomainsIterator;
|
private CircularList<String> topLevelDomainsList;
|
||||||
|
|
||||||
/**
|
|
||||||
* Current index of {@code topLevelDomains} that represents tld we are probing.
|
|
||||||
*/
|
|
||||||
private String currentDomain;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WebWhoisToken(@WebWhoisProtocol ImmutableList<String> topLevelDomains) {
|
public WebWhoisToken(@WebWhoisProtocol CircularList<String> topLevelDomainsList) {
|
||||||
|
|
||||||
topLevelDomainsIterator = topLevelDomains.iterator();
|
this.topLevelDomainsList = topLevelDomainsList;
|
||||||
currentDomain = topLevelDomainsIterator.next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments {@code domainsIndex} or resets it to reflect move to next top level domain.
|
* Moves on to next top level domain in {@code topLevelDomainsList}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public WebWhoisToken next() {
|
public WebWhoisToken next() {
|
||||||
currentDomain = topLevelDomainsIterator.next();
|
topLevelDomainsList = topLevelDomainsList.next();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +70,7 @@ public class WebWhoisToken extends Token {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String host() {
|
public String host() {
|
||||||
return PREFIX + currentDomain;
|
return PREFIX + topLevelDomainsList.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,76 +17,304 @@ 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.doCallRealMethod;
|
import static org.mockito.Mockito.doCallRealMethod;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import google.registry.monitoring.blackbox.exceptions.FailureException;
|
||||||
|
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||||
|
import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException;
|
||||||
import google.registry.monitoring.blackbox.tokens.Token;
|
import google.registry.monitoring.blackbox.tokens.Token;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
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;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit Tests on {@link ProbingSequence}
|
||||||
|
*
|
||||||
|
* <p>First tests the construction of sequences and ensures the ordering is exactly how
|
||||||
|
* we expect it to be.</p>
|
||||||
|
*
|
||||||
|
* <p>Then tests the execution of each step, by ensuring the methods treatment of any kind
|
||||||
|
* of response from the {@link ProbingStep}s or {@link ProbingAction}s is what is expected.</p>
|
||||||
|
*
|
||||||
|
* <p>On every test that runs the sequence, in order for the sequence to stop, we throw an
|
||||||
|
* {@link UnrecoverableStateException}, using mocks of the steps or actions, as the sequences
|
||||||
|
* are run using the main thread (with {@link EmbeddedChannel}).</p>
|
||||||
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class ProbingSequenceTest {
|
public class ProbingSequenceTest {
|
||||||
|
|
||||||
private ProbingStep firstStep;
|
/**
|
||||||
private ProbingStep secondStep;
|
* Default mock {@link ProbingAction} returned when generating an action with a mockStep.
|
||||||
private ProbingStep thirdStep;
|
*/
|
||||||
|
private ProbingAction mockAction = Mockito.mock(ProbingAction.class);
|
||||||
|
|
||||||
private Token testToken;
|
/**
|
||||||
|
* Default mock {@link ProbingStep} that will usually return a {@code mockAction} on call to
|
||||||
|
* generate action.
|
||||||
|
*/
|
||||||
|
private ProbingStep mockStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
private ProbingStep setupMockStep() {
|
|
||||||
ProbingStep mock = Mockito.mock(ProbingStep.class);
|
/**
|
||||||
doCallRealMethod().when(mock).nextStep(any(ProbingStep.class));
|
* Default mock {@link Token} that is passed into each {@link ProbingSequence} tested.
|
||||||
doCallRealMethod().when(mock).nextStep();
|
*/
|
||||||
return mock;
|
private Token mockToken = Mockito.mock(Token.class);
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Default mock {@link Protocol} returned {@code mockStep} and occasionally, other mock {@link
|
||||||
|
* ProbingStep}s.
|
||||||
|
*/
|
||||||
|
private Protocol mockProtocol = Mockito.mock(Protocol.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link EmbeddedChannel} used to create new {@link ChannelPromise} objects returned by mock
|
||||||
|
* {@link ProbingAction}s on their {@code call} methods.
|
||||||
|
*/
|
||||||
|
private EmbeddedChannel channel = new EmbeddedChannel();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
firstStep = setupMockStep();
|
// To avoid a NullPointerException, we must have a protocol return persistent connection as
|
||||||
secondStep = setupMockStep();
|
// false.
|
||||||
thirdStep = setupMockStep();
|
doReturn(true).when(mockProtocol).persistentConnection();
|
||||||
|
|
||||||
testToken = Mockito.mock(Token.class);
|
// In order to avoid a NullPointerException, we must have the protocol returned that stores
|
||||||
|
// persistent connection as false.
|
||||||
|
doReturn(mockProtocol).when(mockStep).protocol();
|
||||||
|
|
||||||
|
// Allows for test if channel is accurately set.
|
||||||
|
doCallRealMethod().when(mockToken).setChannel(any(Channel.class));
|
||||||
|
doCallRealMethod().when(mockToken).channel();
|
||||||
|
|
||||||
|
// Allows call to mockAction to retrieve mocked channel.
|
||||||
|
doReturn(channel).when(mockAction).channel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSequenceBasicConstruction_Success() {
|
public void testSequenceBasicConstruction_Success() {
|
||||||
|
ProbingStep firstStep = Mockito.mock(ProbingStep.class);
|
||||||
|
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
||||||
|
ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
ProbingSequence sequence = new ProbingSequence.Builder(testToken)
|
ProbingSequence sequence = new ProbingSequence.Builder(mockToken)
|
||||||
.addStep(firstStep)
|
.add(firstStep)
|
||||||
.addStep(secondStep)
|
.add(secondStep)
|
||||||
.addStep(thirdStep)
|
.add(thirdStep)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(firstStep.nextStep()).isEqualTo(secondStep);
|
assertThat(sequence.get()).isEqualTo(firstStep);
|
||||||
assertThat(secondStep.nextStep()).isEqualTo(thirdStep);
|
sequence = sequence.next();
|
||||||
assertThat(thirdStep.nextStep()).isEqualTo(firstStep);
|
|
||||||
|
|
||||||
sequence.start();
|
assertThat(sequence.get()).isEqualTo(secondStep);
|
||||||
|
sequence = sequence.next();
|
||||||
|
|
||||||
verify(firstStep, times(1)).accept(testToken);
|
assertThat(sequence.get()).isEqualTo(thirdStep);
|
||||||
|
sequence = sequence.next();
|
||||||
|
|
||||||
|
assertThat(sequence.get()).isEqualTo(firstStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSequenceAdvancedConstruction_Success() {
|
public void testSequenceAdvancedConstruction_Success() {
|
||||||
|
ProbingStep firstStep = Mockito.mock(ProbingStep.class);
|
||||||
|
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
||||||
|
ProbingStep thirdStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
ProbingSequence sequence = new ProbingSequence.Builder(testToken)
|
ProbingSequence sequence = new ProbingSequence.Builder(mockToken)
|
||||||
.addStep(thirdStep)
|
.add(thirdStep)
|
||||||
.addStep(secondStep)
|
.add(secondStep)
|
||||||
.markFirstRepeated()
|
.markFirstRepeated()
|
||||||
.addStep(firstStep)
|
.add(firstStep)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(firstStep.nextStep()).isEqualTo(secondStep);
|
assertThat(sequence.get()).isEqualTo(thirdStep);
|
||||||
assertThat(secondStep.nextStep()).isEqualTo(firstStep);
|
sequence = sequence.next();
|
||||||
assertThat(thirdStep.nextStep()).isEqualTo(secondStep);
|
|
||||||
|
assertThat(sequence.get()).isEqualTo(secondStep);
|
||||||
|
sequence = sequence.next();
|
||||||
|
|
||||||
|
assertThat(sequence.get()).isEqualTo(firstStep);
|
||||||
|
sequence = sequence.next();
|
||||||
|
|
||||||
|
assertThat(sequence.get()).isEqualTo(secondStep);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunStep_Success() throws UndeterminedStateException {
|
||||||
|
//Always returns a succeeded future on call to mockAction.
|
||||||
|
doReturn(channel.newSucceededFuture()).when(mockAction).call();
|
||||||
|
|
||||||
|
// Has mockStep always return mockAction on call to generateAction
|
||||||
|
doReturn(mockAction).when(mockStep).generateAction(any(Token.class));
|
||||||
|
|
||||||
|
//Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on.
|
||||||
|
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
||||||
|
ProbingAction secondAction = Mockito.mock(ProbingAction.class);
|
||||||
|
|
||||||
|
doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(secondAction)
|
||||||
|
.call();
|
||||||
|
doReturn(secondAction).when(secondStep).generateAction(mockToken);
|
||||||
|
|
||||||
|
//Build testable sequence from mocked components.
|
||||||
|
ProbingSequence sequence = new ProbingSequence.Builder(mockToken)
|
||||||
|
.add(mockStep)
|
||||||
|
.add(secondStep)
|
||||||
|
.build();
|
||||||
|
|
||||||
sequence.start();
|
sequence.start();
|
||||||
|
|
||||||
verify(thirdStep, times(1)).accept(testToken);
|
// We expect to have only generated actions from mockStep once, and we expect to have called
|
||||||
|
// this generated action only once, as when we move on to secondStep, it terminates the
|
||||||
|
// sequence.
|
||||||
|
verify(mockStep).generateAction(any(Token.class));
|
||||||
|
verify(mockStep).generateAction(mockToken);
|
||||||
|
verify(mockAction).call();
|
||||||
|
|
||||||
|
// Similarly, we expect to generate actions and call the action from the secondStep once, as
|
||||||
|
// after calling it, the sequence should be terminated
|
||||||
|
verify(secondStep).generateAction(any(Token.class));
|
||||||
|
verify(secondStep).generateAction(mockToken);
|
||||||
|
verify(secondAction).call();
|
||||||
|
|
||||||
|
//We should have modified the token's channel after the first, succeeded step.
|
||||||
|
assertThat(mockToken.channel()).isEqualTo(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunLoop_Success() throws UndeterminedStateException {
|
||||||
|
// Always returns a succeeded future on call to mockAction.
|
||||||
|
doReturn(channel.newSucceededFuture()).when(mockAction).call();
|
||||||
|
|
||||||
|
// Has mockStep always return mockAction on call to generateAction
|
||||||
|
doReturn(mockAction).when(mockStep).generateAction(mockToken);
|
||||||
|
|
||||||
|
// Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on.
|
||||||
|
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
||||||
|
ProbingAction secondAction = Mockito.mock(ProbingAction.class);
|
||||||
|
|
||||||
|
// Necessary for success of ProbingSequence runStep method as it calls get().protocol()
|
||||||
|
doReturn(mockProtocol).when(secondStep).protocol();
|
||||||
|
|
||||||
|
// We ensure that secondStep has necessary attributes to be successful step to pass on to
|
||||||
|
// mockStep once more.
|
||||||
|
doReturn(channel.newSucceededFuture()).when(secondAction).call();
|
||||||
|
doReturn(secondAction).when(secondStep).generateAction(mockToken);
|
||||||
|
|
||||||
|
// We get a secondToken that is returned when we are on our second loop in the sequence. This
|
||||||
|
// will inform mockStep on when to generate a different ProbingAction.
|
||||||
|
Token secondToken = Mockito.mock(Token.class);
|
||||||
|
doReturn(secondToken).when(mockToken).next();
|
||||||
|
|
||||||
|
// The thirdAction we use is made so that when it is called, it will halt the ProbingSequence
|
||||||
|
// by returning an UnrecoverableStateException.
|
||||||
|
ProbingAction thirdAction = Mockito.mock(ProbingAction.class);
|
||||||
|
doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(thirdAction).call();
|
||||||
|
doReturn(thirdAction).when(mockStep).generateAction(secondToken);
|
||||||
|
|
||||||
|
//Build testable sequence from mocked components.
|
||||||
|
ProbingSequence sequence = new ProbingSequence.Builder(mockToken)
|
||||||
|
.add(mockStep)
|
||||||
|
.add(secondStep)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sequence.start();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// on to mockStep the second time, it will terminate the sequence after calling thirdAction.
|
||||||
|
verify(mockStep, times(2)).generateAction(any(Token.class));
|
||||||
|
verify(mockStep).generateAction(mockToken);
|
||||||
|
verify(mockStep).generateAction(secondToken);
|
||||||
|
verify(mockAction).call();
|
||||||
|
verify(thirdAction).call();
|
||||||
|
|
||||||
|
// Similarly, we expect to generate actions and call the action from the secondStep once, as
|
||||||
|
// after calling it, we move on to mockStep again, which terminates the sequence.
|
||||||
|
verify(secondStep).generateAction(any(Token.class));
|
||||||
|
verify(secondStep).generateAction(mockToken);
|
||||||
|
verify(secondAction).call();
|
||||||
|
|
||||||
|
//We should have modified the token's channel after the first, succeeded step.
|
||||||
|
assertThat(mockToken.channel()).isEqualTo(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for when we expect Failure within try catch block of generating and calling a
|
||||||
|
* {@link ProbingAction}.
|
||||||
|
*
|
||||||
|
* @throws UndeterminedStateException - necessary for having mock return anything on a call to
|
||||||
|
* {@code generateAction}.
|
||||||
|
*/
|
||||||
|
private void testActionFailure() throws UndeterminedStateException {
|
||||||
|
//Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on.
|
||||||
|
ProbingStep secondStep = Mockito.mock(ProbingStep.class);
|
||||||
|
|
||||||
|
// We create a second token that when used to generate an action throws an
|
||||||
|
// UnrecoverableStateException to terminate the sequence
|
||||||
|
Token secondToken = Mockito.mock(Token.class);
|
||||||
|
doReturn(secondToken).when(mockToken).next();
|
||||||
|
doThrow(new UnrecoverableStateException("")).when(mockStep).generateAction(secondToken);
|
||||||
|
|
||||||
|
//Build testable sequence from mocked components.
|
||||||
|
ProbingSequence sequence = new ProbingSequence.Builder(mockToken)
|
||||||
|
.add(mockStep)
|
||||||
|
.add(secondStep)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sequence.start();
|
||||||
|
|
||||||
|
// We expect that we have generated actions twice. First, when we actually test generateAction
|
||||||
|
// with an actual call using mockToken, and second when we throw an
|
||||||
|
// UnrecoverableStateException with secondToken.
|
||||||
|
verify(mockStep, times(2)).generateAction(any(Token.class));
|
||||||
|
verify(mockStep).generateAction(mockToken);
|
||||||
|
verify(mockStep).generateAction(secondToken);
|
||||||
|
|
||||||
|
// We should never reach the step where we modify the channel, as it should have failed by then
|
||||||
|
assertThat(mockToken.channel()).isNull();
|
||||||
|
assertThat(secondToken.channel()).isNull();
|
||||||
|
|
||||||
|
// We should never reach the second step, since we fail on the first step, then terminate on
|
||||||
|
// the first step after retrying.
|
||||||
|
verify(secondStep, times(0)).generateAction(any(Token.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunStep_FailureRunning() throws UndeterminedStateException {
|
||||||
|
// Returns a failed future when calling the generated mock action.
|
||||||
|
doReturn(channel.newFailedFuture(new FailureException(""))).when(mockAction).call();
|
||||||
|
|
||||||
|
// Returns mock action on call to generate action for ProbingStep.
|
||||||
|
doReturn(mockAction).when(mockStep).generateAction(mockToken);
|
||||||
|
|
||||||
|
//Tests generic behavior we expect when we fail in generating or calling an action.
|
||||||
|
testActionFailure();
|
||||||
|
|
||||||
|
// We only expect to have called this action once, as we only get it from one generateAction
|
||||||
|
// call.
|
||||||
|
verify(mockAction).call();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunStep_FailureGenerating() throws UndeterminedStateException {
|
||||||
|
// Create a mock first step that returns the dummy action when called to generate an action.
|
||||||
|
doThrow(UndeterminedStateException.class).when(mockStep).generateAction(mockToken);
|
||||||
|
|
||||||
|
testActionFailure();
|
||||||
|
|
||||||
|
// We expect to have never called this action, as we fail each time whenever generating actions.
|
||||||
|
verify(mockAction, times(0)).call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +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 google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY;
|
import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY;
|
||||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||||
|
@ -34,8 +30,7 @@ import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
||||||
import google.registry.monitoring.blackbox.messages.TestMessage;
|
import google.registry.monitoring.blackbox.messages.TestMessage;
|
||||||
import google.registry.monitoring.blackbox.tokens.Token;
|
import google.registry.monitoring.blackbox.tokens.Token;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.buffer.Unpooled;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.EventLoopGroup;
|
import io.netty.channel.EventLoopGroup;
|
||||||
import io.netty.channel.embedded.EmbeddedChannel;
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
@ -43,7 +38,6 @@ import io.netty.channel.local.LocalAddress;
|
||||||
import io.netty.channel.local.LocalChannel;
|
import io.netty.channel.local.LocalChannel;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
@ -62,14 +56,11 @@ public class ProbingStepTest {
|
||||||
private static final int PROTOCOL_PORT = 0;
|
private static final int PROTOCOL_PORT = 0;
|
||||||
private static final String TEST_MESSAGE = "TEST_MESSAGE";
|
private static final String TEST_MESSAGE = "TEST_MESSAGE";
|
||||||
private static final String SECONDARY_TEST_MESSAGE = "SECONDARY_TEST_MESSAGE";
|
private static final String SECONDARY_TEST_MESSAGE = "SECONDARY_TEST_MESSAGE";
|
||||||
private static final LocalAddress ADDRESS = new LocalAddress(ADDRESS_NAME);
|
private static final LocalAddress address = new LocalAddress(ADDRESS_NAME);
|
||||||
|
|
||||||
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
|
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
|
||||||
private final Bootstrap bootstrap = new Bootstrap()
|
private final Bootstrap bootstrap = new Bootstrap()
|
||||||
.group(eventLoopGroup)
|
.group(eventLoopGroup)
|
||||||
.channel(LocalChannel.class);
|
.channel(LocalChannel.class);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for testing how well probing step can create connection to blackbox server
|
* Used for testing how well probing step can create connection to blackbox server
|
||||||
*/
|
*/
|
||||||
|
@ -91,13 +82,53 @@ public class ProbingStepTest {
|
||||||
private Token testToken(String host) throws UndeterminedStateException {
|
private Token testToken(String host) throws UndeterminedStateException {
|
||||||
Token token = Mockito.mock(Token.class);
|
Token token = Mockito.mock(Token.class);
|
||||||
doReturn(host).when(token).host();
|
doReturn(host).when(token).host();
|
||||||
doAnswer(answer -> answer.getArgument(0)).when(token)
|
doAnswer(answer -> ((OutboundMessageType) answer.getArgument(0)).modifyMessage(host))
|
||||||
|
.when(token)
|
||||||
.modifyMessage(any(OutboundMessageType.class));
|
.modifyMessage(any(OutboundMessageType.class));
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNewChannel() throws Exception {
|
public void testProbingActionGenerate_embeddedChannel() throws UndeterminedStateException {
|
||||||
|
// Sets up Protocol to represent existing channel connection.
|
||||||
|
Protocol testProtocol = Protocol.builder()
|
||||||
|
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
|
||||||
|
.setName(PROTOCOL_NAME)
|
||||||
|
.setPort(PROTOCOL_PORT)
|
||||||
|
.setPersistentConnection(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Sets up an embedded channel to contain the two handlers we created already.
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler);
|
||||||
|
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
|
||||||
|
|
||||||
|
// Sets up testToken to return arbitrary value, and the embedded channel. Used for when the
|
||||||
|
// ProbingStep generates an ExistingChannelAction.
|
||||||
|
Token testToken = testToken(SECONDARY_TEST_MESSAGE);
|
||||||
|
doReturn(channel).when(testToken).channel();
|
||||||
|
|
||||||
|
// Sets up generic {@link ProbingStep} that we are testing.
|
||||||
|
ProbingStep testStep = ProbingStep.builder()
|
||||||
|
.setMessageTemplate(new TestMessage(TEST_MESSAGE))
|
||||||
|
.setBootstrap(bootstrap)
|
||||||
|
.setDuration(Duration.ZERO)
|
||||||
|
.setProtocol(testProtocol)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ProbingAction testAction = testStep.generateAction(testToken);
|
||||||
|
|
||||||
|
assertThat(testAction.channel()).isEqualTo(channel);
|
||||||
|
assertThat(testAction.delay()).isEqualTo(Duration.ZERO);
|
||||||
|
assertThat(testAction.outboundMessage().toString()).isEqualTo(SECONDARY_TEST_MESSAGE);
|
||||||
|
assertThat(testAction.host()).isEqualTo(SECONDARY_TEST_MESSAGE);
|
||||||
|
assertThat(testAction.protocol()).isEqualTo(testProtocol);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProbingActionGenerate_newChannel() throws UndeterminedStateException {
|
||||||
// Sets up Protocol for when we create a new channel.
|
// Sets up Protocol for when we create a new channel.
|
||||||
Protocol testProtocol = Protocol.builder()
|
Protocol testProtocol = Protocol.builder()
|
||||||
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
|
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
|
||||||
|
@ -106,97 +137,32 @@ public class ProbingStepTest {
|
||||||
.setPersistentConnection(false)
|
.setPersistentConnection(false)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Sets up our main step (firstStep) and throwaway step (dummyStep).
|
// Sets up generic ProbingStep that we are testing.
|
||||||
ProbingStep firstStep = ProbingStep.builder()
|
ProbingStep testStep = ProbingStep.builder()
|
||||||
|
.setMessageTemplate(new TestMessage(TEST_MESSAGE))
|
||||||
.setBootstrap(bootstrap)
|
.setBootstrap(bootstrap)
|
||||||
.setDuration(Duration.ZERO)
|
.setDuration(Duration.ZERO)
|
||||||
.setMessageTemplate(new TestMessage(TEST_MESSAGE))
|
|
||||||
.setProtocol(testProtocol)
|
.setProtocol(testProtocol)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
//Sets up mock dummy step that returns succeeded promise when we successfully reach it.
|
|
||||||
ProbingStep dummyStep = Mockito.mock(ProbingStep.class);
|
|
||||||
|
|
||||||
firstStep.nextStep(dummyStep);
|
|
||||||
|
|
||||||
// Sets up testToken to return arbitrary values, and no channel. Used when we create a new
|
// Sets up testToken to return arbitrary values, and no channel. Used when we create a new
|
||||||
// channel.
|
// channel.
|
||||||
Token testToken = testToken(ADDRESS_NAME);
|
Token testToken = testToken(ADDRESS_NAME);
|
||||||
|
|
||||||
//Set up blackbox server that receives our messages then echoes them back to us
|
// Sets up server listening at LocalAddress so generated action can have successful connection.
|
||||||
nettyRule.setUpServer(ADDRESS);
|
nettyRule.setUpServer(address);
|
||||||
|
|
||||||
//checks that the ProbingSteps are appropriately pointing to each other
|
ProbingAction testAction = testStep.generateAction(testToken);
|
||||||
assertThat(firstStep.nextStep()).isEqualTo(dummyStep);
|
|
||||||
|
|
||||||
//Call accept on the first step, which should send our message to the server, which will then be
|
ChannelFuture connectionFuture = testAction.channel().attr(CONNECTION_FUTURE_KEY).get();
|
||||||
//echoed back to us, causing us to move to the next step
|
connectionFuture = connectionFuture.syncUninterruptibly();
|
||||||
firstStep.accept(testToken);
|
|
||||||
|
|
||||||
//checks that we have appropriately sent the write message to server
|
assertThat(connectionFuture.isSuccess()).isTrue();
|
||||||
nettyRule.assertReceivedMessage(TEST_MESSAGE);
|
assertThat(testAction.delay()).isEqualTo(Duration.ZERO);
|
||||||
|
assertThat(testAction.outboundMessage().toString()).isEqualTo(ADDRESS_NAME);
|
||||||
|
assertThat(testAction.host()).isEqualTo(ADDRESS_NAME);
|
||||||
|
assertThat(testAction.protocol()).isEqualTo(testProtocol);
|
||||||
|
|
||||||
//checks that when the future is successful, we pass down the requisite token
|
|
||||||
verify(dummyStep, times(1)).accept(any(Token.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO - Currently, this test fails to receive outbound messages from the embedded channel, which
|
|
||||||
// we will fix in a later release.
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void testWithSequence_ExistingChannel() throws Exception {
|
|
||||||
// Sets up Protocol for when a channel already exists.
|
|
||||||
Protocol testProtocol = Protocol.builder()
|
|
||||||
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
|
|
||||||
.setName(PROTOCOL_NAME)
|
|
||||||
.setPort(PROTOCOL_PORT)
|
|
||||||
.setPersistentConnection(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Sets up our main step (firstStep) and throwaway step (dummyStep).
|
|
||||||
ProbingStep firstStep = ProbingStep.builder()
|
|
||||||
.setBootstrap(bootstrap)
|
|
||||||
.setDuration(Duration.ZERO)
|
|
||||||
.setMessageTemplate(new TestMessage(TEST_MESSAGE))
|
|
||||||
.setProtocol(testProtocol)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
//Sets up mock dummy step that returns succeeded promise when we successfully reach it.
|
|
||||||
ProbingStep dummyStep = Mockito.mock(ProbingStep.class);
|
|
||||||
|
|
||||||
firstStep.nextStep(dummyStep);
|
|
||||||
|
|
||||||
// Sets up an embedded channel to contain the two handlers we created already.
|
|
||||||
EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler);
|
|
||||||
|
|
||||||
//Assures that the channel has a succeeded connectionFuture.
|
|
||||||
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
|
|
||||||
|
|
||||||
// Sets up testToken to return arbitrary value, and the embedded channel. Used for when the
|
|
||||||
// ProbingStep generates an ExistingChannelAction.
|
|
||||||
Token testToken = testToken("");
|
|
||||||
doReturn(channel).when(testToken).channel();
|
|
||||||
|
|
||||||
//checks that the ProbingSteps are appropriately pointing to each other
|
|
||||||
assertThat(firstStep.nextStep()).isEqualTo(dummyStep);
|
|
||||||
|
|
||||||
//Call accept on the first step, which should send our message through the EmbeddedChannel
|
|
||||||
// pipeline
|
|
||||||
firstStep.accept(testToken);
|
|
||||||
|
|
||||||
Object msg = channel.readOutbound();
|
|
||||||
|
|
||||||
while (msg == null) {
|
|
||||||
msg = channel.readOutbound();
|
|
||||||
}
|
|
||||||
//Ensures the accurate message is sent down the pipeline
|
|
||||||
assertThat(((ByteBuf) channel.readOutbound()).toString(UTF_8)).isEqualTo(TEST_MESSAGE);
|
|
||||||
|
|
||||||
//Write response to our message down EmbeddedChannel pipeline
|
|
||||||
channel.writeInbound(Unpooled.wrappedBuffer(SECONDARY_TEST_MESSAGE.getBytes(US_ASCII)));
|
|
||||||
|
|
||||||
//At this point, we should have received the message, so the future obtained should be marked
|
|
||||||
// as a success
|
|
||||||
verify(dummyStep, times(1)).accept(any(Token.class));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ package google.registry.monitoring.blackbox.tokens;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||||
import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
|
import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
|
||||||
|
import google.registry.util.CircularList;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
@ -29,15 +29,17 @@ import org.junit.runners.JUnit4;
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class WebWhoisTokenTest {
|
public class WebWhoisTokenTest {
|
||||||
|
|
||||||
private static String PREFIX = "whois.nic.";
|
private static final String PREFIX = "whois.nic.";
|
||||||
private static String HOST = "starter";
|
private static final String HOST = "starter";
|
||||||
private static String FIRST_TLD = "first_test";
|
private static final String FIRST_TLD = "first_test";
|
||||||
private static String SECOND_TLD = "second_test";
|
private static final String SECOND_TLD = "second_test";
|
||||||
private static String THIRD_TLD = "third_test";
|
private static final String THIRD_TLD = "third_test";
|
||||||
private static ImmutableList<String> TEST_DOMAINS = ImmutableList.of(FIRST_TLD, SECOND_TLD,
|
private final CircularList<String> testDomains =
|
||||||
THIRD_TLD);
|
new CircularList.Builder<String>()
|
||||||
|
.add(FIRST_TLD, SECOND_TLD, THIRD_TLD)
|
||||||
|
.build();
|
||||||
|
|
||||||
public Token webToken = new WebWhoisToken(TEST_DOMAINS);
|
public Token webToken = new WebWhoisToken(testDomains);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMessageModification() throws UndeterminedStateException {
|
public void testMessageModification() throws UndeterminedStateException {
|
||||||
|
@ -47,7 +49,7 @@ public class WebWhoisTokenTest {
|
||||||
|
|
||||||
//attempts to use Token's method for modifying the method based on its stored host
|
//attempts to use Token's method for modifying the method based on its stored host
|
||||||
HttpRequestMessage secondMessage = (HttpRequestMessage) webToken.modifyMessage(message);
|
HttpRequestMessage secondMessage = (HttpRequestMessage) webToken.modifyMessage(message);
|
||||||
assertThat(secondMessage.headers().get("host")).isEqualTo(PREFIX + TEST_DOMAINS.get(0));
|
assertThat(secondMessage.headers().get("host")).isEqualTo(PREFIX + FIRST_TLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,6 +65,9 @@ public class WebWhoisTokenTest {
|
||||||
webToken = webToken.next();
|
webToken = webToken.next();
|
||||||
|
|
||||||
assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD);
|
assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD);
|
||||||
|
webToken = webToken.next();
|
||||||
|
|
||||||
|
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
146
util/src/main/java/google/registry/util/CircularList.java
Normal file
146
util/src/main/java/google/registry/util/CircularList.java
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// 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.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that stores value {@param <T>}, and points in circle to other {@link CircularList}
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* @param <T> - Element type stored in the {@link CircularList}
|
||||||
|
*
|
||||||
|
* <p>In its construction, we create a sequence of {@link CircularList} objects, each storing an
|
||||||
|
* instance of T. They each point to each other in a circular manner, such that we can perform
|
||||||
|
* circular iteration on the elements. Once finished building, we return this first {@link
|
||||||
|
* CircularList} object in the sequence.</p>
|
||||||
|
*/
|
||||||
|
public class CircularList<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T instance stored in current node of list.
|
||||||
|
*/
|
||||||
|
private final T value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointer to next node of list.
|
||||||
|
*/
|
||||||
|
private CircularList<T> next;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard constructor for {@link CircularList} that initializes its stored {@code value}.
|
||||||
|
*/
|
||||||
|
protected CircularList(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard get method to retrieve {@code value}.
|
||||||
|
*/
|
||||||
|
public T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard method to obtain {@code next} node in list.
|
||||||
|
*/
|
||||||
|
public CircularList<T> next() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter method only used in builder to point one node to the next.
|
||||||
|
*/
|
||||||
|
public void setNext(CircularList<T> next) {
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Builder to create a standard instance of a {@link CircularList}.
|
||||||
|
*/
|
||||||
|
public static class Builder<T> extends AbstractBuilder<T, CircularList<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply calls on constructor to {@link CircularList} to create new instance.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected CircularList<T> create(T value) {
|
||||||
|
return new CircularList<>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As {@link CircularList} represents one component of the entire list, it requires a builder to
|
||||||
|
* create the full list.
|
||||||
|
*
|
||||||
|
* @param <T> - Matching element type of iterator
|
||||||
|
*
|
||||||
|
* <p>Supports adding in element at a time, adding an {@link Iterable}
|
||||||
|
* of elements, and adding an variable number of elemetns.</p>
|
||||||
|
*
|
||||||
|
* <p>Sets first element added to {@code first}, and when built, points last element to the
|
||||||
|
* {@code first} element.</p>
|
||||||
|
*/
|
||||||
|
public abstract static class AbstractBuilder<T, C extends CircularList<T>> {
|
||||||
|
|
||||||
|
protected C first;
|
||||||
|
protected C current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Necessary to instantiate each {@code C} object from {@code value}.
|
||||||
|
*/
|
||||||
|
protected abstract C create(T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets current {@code C} to element added and points previous {@code C} to this one.
|
||||||
|
*/
|
||||||
|
public AbstractBuilder<T, C> add(T value) {
|
||||||
|
C c = create(value);
|
||||||
|
if (current == null) {
|
||||||
|
first = c;
|
||||||
|
} else {
|
||||||
|
current.setNext(c);
|
||||||
|
}
|
||||||
|
current = c;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply calls {@code addElement}, for each element in {@code elements}.
|
||||||
|
*/
|
||||||
|
public AbstractBuilder<T, C> add(T... values) {
|
||||||
|
for (T element : values) {
|
||||||
|
add(element);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply calls {@code addElement}, for each element in {@code elements}.
|
||||||
|
*/
|
||||||
|
public AbstractBuilder<T, C> add(Iterable<T> values) {
|
||||||
|
values.forEach(this::add);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the next {@code C} of the list to be the first {@code C} in the list.
|
||||||
|
*/
|
||||||
|
public C build() {
|
||||||
|
current.setNext(first);
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue