From df5f450435ab49c4e36fcb679dafc61b41661f49 Mon Sep 17 00:00:00 2001 From: Aman Sanger Date: Mon, 12 Aug 2019 18:41:50 -0400 Subject: [PATCH] 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 --- .../monitoring/blackbox/ProbingAction.java | 8 +- .../monitoring/blackbox/ProbingSequence.java | 199 ++++++++++-- .../monitoring/blackbox/ProbingStep.java | 96 +----- .../monitoring/blackbox/WebWhoisModule.java | 12 +- .../UnrecoverableStateException.java | 30 ++ .../blackbox/tokens/WebWhoisToken.java | 20 +- .../blackbox/ProbingSequenceTest.java | 290 ++++++++++++++++-- .../monitoring/blackbox/ProbingStepTest.java | 124 +++----- .../blackbox/tokens/WebWhoisTokenTest.java | 25 +- .../google/registry/util/CircularList.java | 146 +++++++++ 10 files changed, 683 insertions(+), 267 deletions(-) create mode 100644 prober/src/main/java/google/registry/monitoring/blackbox/exceptions/UnrecoverableStateException.java create mode 100644 util/src/main/java/google/registry/util/CircularList.java diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java index 8173ba6baee..bfda982329e 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java @@ -135,12 +135,12 @@ private static void addHandlers( * when the connection future is successful.

* *

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 - * we receive an expected response from the server.

+ * the {@link ActionHandler}.From that, we can obtain a future that is marked as a success when we + * receive an expected response from the server.

* *

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, - * we inform the {@link ProbingStep} of this.

+ * {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we + * inform the {@link ProbingStep} of this.

* * @return {@link ChannelFuture} that denotes when the action has been successfully performed. */ diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java index 7f97f8bd519..c9757c00673 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java @@ -14,14 +14,25 @@ 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.util.CircularList; import io.netty.bootstrap.Bootstrap; import io.netty.channel.AbstractChannel; +import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; /** * Represents Sequence of {@link ProbingStep}s that the Prober performs in order. * + *

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 -> ... + *

* *

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 @@ -31,78 +42,202 @@ * the first one is activated with the requisite {@link Token}, the {@link ProbingStep}s do the rest * of the work.

*/ -public class ProbingSequence { +public class ProbingSequence extends CircularList { - private ProbingStep firstStep; + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); /** * Each {@link ProbingSequence} requires a start token to begin running. */ 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; } + /** + * 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() { - // calls the first step with startToken; - firstStep.accept(startToken); + runStep(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}. + * + *

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. + * + *

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.

+ */ + 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 * Bootstrap}. */ - public static class Builder { + public static class Builder extends CircularList.AbstractBuilder { - private ProbingStep currentStep; - private ProbingStep firstStep; - private ProbingStep firstRepeatedStep; + private ProbingSequence firstRepeatedSequenceStep; private Token startToken; + /** + * This Builder must also be supplied with a {@link Token} to construct a {@link + * ProbingSequence}. + */ public Builder(Token startToken) { this.startToken = startToken; } /** - * Adds {@link ProbingStep}, which is supplied with {@link Bootstrap}, built, and pointed to by - * the previous {@link ProbingStep} added. + * We take special note of the first repeated step. */ - public Builder addStep(ProbingStep step) { + public Builder markFirstRepeated() { + firstRepeatedSequenceStep = current; + return this; + } - if (currentStep == null) { - firstStep = step; - } else { - currentStep.nextStep(step); - } + @Override + public Builder add(ProbingStep value) { + super.add(value); + current.first = first; - currentStep = step; return this; } - /** - * We take special note of the first repeated step. - */ - public Builder markFirstRepeated() { - firstRepeatedStep = currentStep; - 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}. */ + @Override public ProbingSequence build() { - if (firstRepeatedStep == null) { - firstRepeatedStep = firstStep; + if (firstRepeatedSequenceStep == null) { + firstRepeatedSequenceStep = first; } - currentStep.nextStep(firstRepeatedStep); - currentStep.lastStep(); - return new ProbingSequence(this.firstStep, this.startToken); + current.markLast(); + current.setNext(firstRepeatedSequenceStep); + return first; } } } - diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java index 35bb141246a..614e6740341 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java @@ -15,13 +15,10 @@ package google.registry.monitoring.blackbox; import com.google.auto.value.AutoValue; -import com.google.common.flogger.FluentLogger; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.messages.OutboundMessageType; import google.registry.monitoring.blackbox.tokens.Token; import io.netty.bootstrap.Bootstrap; -import io.netty.channel.ChannelFuture; -import java.util.function.Consumer; import org.joda.time.Duration; /** @@ -34,15 +31,7 @@ * Token} and from that, generates a new {@link ProbingAction} to call.

*/ @AutoValue -public abstract class ProbingStep implements Consumer { - - 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 abstract class ProbingStep { public static Builder builder() { return new AutoValue_ProbingStep.Builder(); @@ -69,22 +58,10 @@ public static Builder builder() { */ 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} */ - private ProbingAction generateAction(Token token) throws UndeterminedStateException { + public ProbingAction generateAction(Token token) throws UndeterminedStateException { OutboundMessageType message = token.modifyMessage(messageTemplate()); ProbingAction.Builder probingActionBuilder = ProbingAction.builder() .setDelay(duration()) @@ -101,71 +78,6 @@ private ProbingAction generateAction(Token token) throws UndeterminedStateExcept 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}. - * - *

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.

- */ - @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 public final String toString() { return String.format("ProbingStep with Protocol: %s\n" @@ -175,7 +87,7 @@ public final String toString() { } /** - * Default {@link AutoValue.Builder} for {@link ProbingStep}. + * Standard {@link AutoValue.Builder} for {@link ProbingStep}. */ @AutoValue.Builder public abstract static class Builder { @@ -190,7 +102,5 @@ public abstract static class Builder { public abstract ProbingStep build(); } - } - diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java b/prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java index b4f41736d5b..1b9a7eb644c 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java @@ -23,6 +23,7 @@ import google.registry.monitoring.blackbox.handlers.WebWhoisMessageHandler; import google.registry.monitoring.blackbox.messages.HttpRequestMessage; import google.registry.monitoring.blackbox.tokens.WebWhoisToken; +import google.registry.util.CircularList; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; @@ -189,7 +190,7 @@ ProbingSequence provideWebWhoisSequence( WebWhoisToken webWhoisToken) { return new ProbingSequence.Builder(webWhoisToken) - .addStep(probingStep) + .add(probingStep) .build(); } @@ -205,8 +206,10 @@ int provideMaximumMessageLengthBytes() { @Singleton @Provides @WebWhoisProtocol - ImmutableList provideTopLevelDomains() { - return ImmutableList.of("how", "soy", "xn--q9jyb4c"); + CircularList provideTopLevelDomains() { + return new CircularList.Builder() + .add("how", "soy", "xn--q9jyb4c") + .build(); } @Provides @@ -244,6 +247,5 @@ int provideHttpsWhoisPort() { public @interface WebWhoisProtocol { } - - } + diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/UnrecoverableStateException.java b/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/UnrecoverableStateException.java new file mode 100644 index 00000000000..a9c747f9094 --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/UnrecoverableStateException.java @@ -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); + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java index 3ff852f3556..91160f5c667 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java @@ -18,7 +18,7 @@ import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.messages.OutboundMessageType; -import java.util.Iterator; +import google.registry.util.CircularList; import javax.inject.Inject; /** @@ -38,26 +38,20 @@ public class WebWhoisToken extends Token { /** * {@link ImmutableList} of all top level domains to be probed. */ - private final Iterator topLevelDomainsIterator; - - /** - * Current index of {@code topLevelDomains} that represents tld we are probing. - */ - private String currentDomain; + private CircularList topLevelDomainsList; @Inject - public WebWhoisToken(@WebWhoisProtocol ImmutableList topLevelDomains) { + public WebWhoisToken(@WebWhoisProtocol CircularList topLevelDomainsList) { - topLevelDomainsIterator = topLevelDomains.iterator(); - currentDomain = topLevelDomainsIterator.next(); + this.topLevelDomainsList = topLevelDomainsList; } /** - * 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 public WebWhoisToken next() { - currentDomain = topLevelDomainsIterator.next(); + topLevelDomainsList = topLevelDomainsList.next(); return this; } @@ -76,7 +70,7 @@ public OutboundMessageType modifyMessage(OutboundMessageType original) */ @Override public String host() { - return PREFIX + currentDomain; + return PREFIX + topLevelDomainsList.get(); } } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java index 02599bcb51a..97ea630f939 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java @@ -17,76 +17,304 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; 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.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 io.netty.channel.Channel; +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +/** + * Unit Tests on {@link ProbingSequence} + * + *

First tests the construction of sequences and ensures the ordering is exactly how + * we expect it to be.

+ * + *

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.

+ * + *

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}).

+ */ @RunWith(JUnit4.class) public class ProbingSequenceTest { - private ProbingStep firstStep; - private ProbingStep secondStep; - private ProbingStep thirdStep; + /** + * Default mock {@link ProbingAction} returned when generating an action with a mockStep. + */ + 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)); - doCallRealMethod().when(mock).nextStep(); - return mock; - } + + /** + * Default mock {@link Token} that is passed into each {@link ProbingSequence} tested. + */ + 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 public void setup() { - firstStep = setupMockStep(); - secondStep = setupMockStep(); - thirdStep = setupMockStep(); + // To avoid a NullPointerException, we must have a protocol return persistent connection as + // false. + 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 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) - .addStep(firstStep) - .addStep(secondStep) - .addStep(thirdStep) + ProbingSequence sequence = new ProbingSequence.Builder(mockToken) + .add(firstStep) + .add(secondStep) + .add(thirdStep) .build(); - assertThat(firstStep.nextStep()).isEqualTo(secondStep); - assertThat(secondStep.nextStep()).isEqualTo(thirdStep); - assertThat(thirdStep.nextStep()).isEqualTo(firstStep); + assertThat(sequence.get()).isEqualTo(firstStep); + sequence = sequence.next(); - sequence.start(); + assertThat(sequence.get()).isEqualTo(secondStep); + sequence = sequence.next(); + + assertThat(sequence.get()).isEqualTo(thirdStep); + sequence = sequence.next(); - verify(firstStep, times(1)).accept(testToken); + assertThat(sequence.get()).isEqualTo(firstStep); } @Test 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) - .addStep(thirdStep) - .addStep(secondStep) + ProbingSequence sequence = new ProbingSequence.Builder(mockToken) + .add(thirdStep) + .add(secondStep) .markFirstRepeated() - .addStep(firstStep) + .add(firstStep) .build(); - assertThat(firstStep.nextStep()).isEqualTo(secondStep); - assertThat(secondStep.nextStep()).isEqualTo(firstStep); - assertThat(thirdStep.nextStep()).isEqualTo(secondStep); + assertThat(sequence.get()).isEqualTo(thirdStep); + sequence = sequence.next(); + + 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(); - 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(); + } } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java index db7ec271956..132f19f8b48 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java @@ -16,13 +16,9 @@ import static com.google.common.truth.Truth.assertThat; 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.Mockito.doAnswer; 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 google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; @@ -34,8 +30,7 @@ import google.registry.monitoring.blackbox.messages.TestMessage; import google.registry.monitoring.blackbox.tokens.Token; import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.EventLoopGroup; import io.netty.channel.embedded.EmbeddedChannel; @@ -43,7 +38,6 @@ import io.netty.channel.local.LocalChannel; import io.netty.channel.nio.NioEventLoopGroup; import org.joda.time.Duration; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; @@ -62,14 +56,11 @@ public class ProbingStepTest { private static final int PROTOCOL_PORT = 0; private static final String TEST_MESSAGE = "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 Bootstrap bootstrap = new Bootstrap() .group(eventLoopGroup) .channel(LocalChannel.class); - - /** * Used for testing how well probing step can create connection to blackbox server */ @@ -91,112 +82,87 @@ public class ProbingStepTest { private Token testToken(String host) throws UndeterminedStateException { Token token = Mockito.mock(Token.class); 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)); + return token; } @Test - public void testNewChannel() throws Exception { - // Sets up Protocol for when we create a new channel. + 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(false) + .setPersistentConnection(true) .build(); - // Sets up our main step (firstStep) and throwaway step (dummyStep). - ProbingStep firstStep = ProbingStep.builder() + // 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) - .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 testToken to return arbitrary values, and no channel. Used when we create a new - // channel. - Token testToken = testToken(ADDRESS_NAME); - - //Set up blackbox server that receives our messages then echoes them back to us - nettyRule.setUpServer(ADDRESS); - - //checks that the ProbingSteps are appropriately pointing to each other - assertThat(firstStep.nextStep()).isEqualTo(dummyStep); + ProbingAction testAction = testStep.generateAction(testToken); - //Call accept on the first step, which should send our message to the server, which will then be - //echoed back to us, causing us to move to the next step - firstStep.accept(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); - //checks that we have appropriately sent the write message to server - nettyRule.assertReceivedMessage(TEST_MESSAGE); - //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. + public void testProbingActionGenerate_newChannel() throws UndeterminedStateException { + // Sets up Protocol for when we create a new channel. Protocol testProtocol = Protocol.builder() .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) .setName(PROTOCOL_NAME) .setPort(PROTOCOL_PORT) - .setPersistentConnection(true) + .setPersistentConnection(false) .build(); - // Sets up our main step (firstStep) and throwaway step (dummyStep). - ProbingStep firstStep = ProbingStep.builder() + // Sets up generic ProbingStep that we are testing. + ProbingStep testStep = ProbingStep.builder() + .setMessageTemplate(new TestMessage(TEST_MESSAGE)) .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(); + // Sets up testToken to return arbitrary values, and no channel. Used when we create a new + // channel. + Token testToken = testToken(ADDRESS_NAME); - //checks that the ProbingSteps are appropriately pointing to each other - assertThat(firstStep.nextStep()).isEqualTo(dummyStep); + // Sets up server listening at LocalAddress so generated action can have successful connection. + nettyRule.setUpServer(address); - //Call accept on the first step, which should send our message through the EmbeddedChannel - // pipeline - firstStep.accept(testToken); + ProbingAction testAction = testStep.generateAction(testToken); - Object msg = channel.readOutbound(); + ChannelFuture connectionFuture = testAction.channel().attr(CONNECTION_FUTURE_KEY).get(); + connectionFuture = connectionFuture.syncUninterruptibly(); - 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); + assertThat(connectionFuture.isSuccess()).isTrue(); + assertThat(testAction.delay()).isEqualTo(Duration.ZERO); + assertThat(testAction.outboundMessage().toString()).isEqualTo(ADDRESS_NAME); + assertThat(testAction.host()).isEqualTo(ADDRESS_NAME); + assertThat(testAction.protocol()).isEqualTo(testProtocol); - //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)); } } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java index 9a3e755364b..148558a2bee 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java @@ -16,9 +16,9 @@ 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.messages.HttpRequestMessage; +import google.registry.util.CircularList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -29,15 +29,17 @@ @RunWith(JUnit4.class) public class WebWhoisTokenTest { - private static String PREFIX = "whois.nic."; - private static String HOST = "starter"; - private static String FIRST_TLD = "first_test"; - private static String SECOND_TLD = "second_test"; - private static String THIRD_TLD = "third_test"; - private static ImmutableList TEST_DOMAINS = ImmutableList.of(FIRST_TLD, SECOND_TLD, - THIRD_TLD); + private static final String PREFIX = "whois.nic."; + private static final String HOST = "starter"; + private static final String FIRST_TLD = "first_test"; + private static final String SECOND_TLD = "second_test"; + private static final String THIRD_TLD = "third_test"; + private final CircularList testDomains = + new CircularList.Builder() + .add(FIRST_TLD, SECOND_TLD, THIRD_TLD) + .build(); - public Token webToken = new WebWhoisToken(TEST_DOMAINS); + public Token webToken = new WebWhoisToken(testDomains); @Test public void testMessageModification() throws UndeterminedStateException { @@ -47,7 +49,7 @@ public void testMessageModification() throws UndeterminedStateException { //attempts to use Token's method for modifying the method based on its stored host 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 void testNextToken() { webToken = webToken.next(); assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD); + webToken = webToken.next(); + + assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD); } } diff --git a/util/src/main/java/google/registry/util/CircularList.java b/util/src/main/java/google/registry/util/CircularList.java new file mode 100644 index 00000000000..86f6229f439 --- /dev/null +++ b/util/src/main/java/google/registry/util/CircularList.java @@ -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 }, and points in circle to other {@link CircularList} + * objects. + * + * @param - Element type stored in the {@link CircularList} + * + *

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.

+ */ +public class CircularList { + + /** + * T instance stored in current node of list. + */ + private final T value; + + /** + * Pointer to next node of list. + */ + private CircularList 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 next() { + return next; + } + + /** + * Setter method only used in builder to point one node to the next. + */ + public void setNext(CircularList next) { + this.next = next; + } + + /** + * Default Builder to create a standard instance of a {@link CircularList}. + */ + public static class Builder extends AbstractBuilder> { + + /** + * Simply calls on constructor to {@link CircularList} to create new instance. + */ + @Override + protected CircularList 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 - Matching element type of iterator + * + *

Supports adding in element at a time, adding an {@link Iterable} + * of elements, and adding an variable number of elemetns.

+ * + *

Sets first element added to {@code first}, and when built, points last element to the + * {@code first} element.

+ */ + public abstract static class AbstractBuilder> { + + 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 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 add(T... values) { + for (T element : values) { + add(element); + } + return this; + } + + /** + * Simply calls {@code addElement}, for each element in {@code elements}. + */ + public AbstractBuilder add(Iterable 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; + } + } + +}