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:
Aman Sanger 2019-08-12 18:41:50 -04:00 committed by GitHub
parent 5b2ce06b79
commit 02c2b37fe3
10 changed files with 697 additions and 281 deletions

View file

@ -135,12 +135,12 @@ public abstract class ProbingAction implements Callable<ChannelFuture> {
* when the connection future is successful.</p>
*
* <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
* we receive an expected response from the server.</p>
* 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.</p>
*
* <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,
* we inform the {@link ProbingStep} of this.</p>
* {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we
* inform the {@link ProbingStep} of this.</p>
*
* @return {@link ChannelFuture} that denotes when the action has been successfully performed.
*/

View file

@ -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.
*
* <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}
* 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
* 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.
*/
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}.
*
* <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
* Bootstrap}.
*/
public static class Builder {
public static class Builder extends CircularList.AbstractBuilder<ProbingStep, ProbingSequence> {
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.
*/
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.
*/
public Builder markFirstRepeated() {
firstRepeatedStep = currentStep;
firstRepeatedSequenceStep = current;
return this;
}
/**
* Points last {@link ProbingStep} to the {@code firstRepeatedStep} and calls private
* constructor to create {@link ProbingSequence}.
*/
public ProbingSequence build() {
if (firstRepeatedStep == null) {
firstRepeatedStep = firstStep;
@Override
public Builder add(ProbingStep value) {
super.add(value);
current.first = first;
return this;
}
currentStep.nextStep(firstRepeatedStep);
currentStep.lastStep();
return new ProbingSequence(this.firstStep, this.startToken);
@Override
protected ProbingSequence create(ProbingStep value) {
return new ProbingSequence(value, startToken);
}
/**
* Points last {@link ProbingStep} to the {@code firstRepeatedSequenceStep} and calls private
* constructor to create {@link ProbingSequence}.
*/
@Override
public ProbingSequence build() {
if (firstRepeatedSequenceStep == null) {
firstRepeatedSequenceStep = first;
}
current.markLast();
current.setNext(firstRepeatedSequenceStep);
return first;
}
}
}

View file

@ -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 @@ import org.joda.time.Duration;
* Token} and from that, generates a new {@link ProbingAction} to call.</p>
*/
@AutoValue
public abstract class ProbingStep implements Consumer<Token> {
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 abstract class ProbingStep implements Consumer<Token> {
*/
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 @@ public abstract class ProbingStep implements Consumer<Token> {
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
public final String toString() {
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
public abstract static class Builder {
@ -190,7 +102,5 @@ public abstract class ProbingStep implements Consumer<Token> {
public abstract ProbingStep build();
}
}

View file

@ -23,6 +23,7 @@ import google.registry.monitoring.blackbox.handlers.WebWhoisActionHandler;
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 @@ public class WebWhoisModule {
WebWhoisToken webWhoisToken) {
return new ProbingSequence.Builder(webWhoisToken)
.addStep(probingStep)
.add(probingStep)
.build();
}
@ -205,8 +206,10 @@ public class WebWhoisModule {
@Singleton
@Provides
@WebWhoisProtocol
ImmutableList<String> provideTopLevelDomains() {
return ImmutableList.of("how", "soy", "xn--q9jyb4c");
CircularList<String> provideTopLevelDomains() {
return new CircularList.Builder<String>()
.add("how", "soy", "xn--q9jyb4c")
.build();
}
@Provides
@ -244,6 +247,5 @@ public class WebWhoisModule {
public @interface WebWhoisProtocol {
}
}

View file

@ -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);
}
}

View file

@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableList;
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<String> topLevelDomainsIterator;
/**
* Current index of {@code topLevelDomains} that represents tld we are probing.
*/
private String currentDomain;
private CircularList<String> topLevelDomainsList;
@Inject
public WebWhoisToken(@WebWhoisProtocol ImmutableList<String> topLevelDomains) {
public WebWhoisToken(@WebWhoisProtocol CircularList<String> 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 class WebWhoisToken extends Token {
*/
@Override
public String host() {
return PREFIX + currentDomain;
return PREFIX + topLevelDomainsList.get();
}
}

View file

@ -17,76 +17,304 @@ package google.registry.monitoring.blackbox;
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}
*
* <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)
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();
verify(firstStep, times(1)).accept(testToken);
assertThat(sequence.get()).isEqualTo(thirdStep);
sequence = sequence.next();
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();
}
}

View file

@ -16,13 +16,9 @@ package google.registry.monitoring.blackbox;
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.OutboundMessageType;
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.LocalAddress;
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,13 +82,53 @@ 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 {
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.
Protocol testProtocol = Protocol.builder()
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
@ -106,97 +137,32 @@ public class ProbingStepTest {
.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 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);
// Sets up server listening at LocalAddress so generated action can have successful connection.
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);
ChannelFuture connectionFuture = testAction.channel().attr(CONNECTION_FUTURE_KEY).get();
connectionFuture = connectionFuture.syncUninterruptibly();
//checks that we have appropriately sent the write message to server
nettyRule.assertReceivedMessage(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);
//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));
}
}

View file

@ -16,9 +16,9 @@ package google.registry.monitoring.blackbox.tokens;
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 @@ import org.junit.runners.JUnit4;
@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<String> 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<String> testDomains =
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
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
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();
assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD);
webToken = webToken.next();
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
}
}

View 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;
}
}
}