mirror of
https://github.com/google/nomulus.git
synced 2025-07-06 11:13:35 +02:00
Add full prober WebWHOIS sequence functionality (#180)
* 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 Changes Suggested by jianglai * Fixed Gitignore to take out AutoValue generated code * Removed AutoValue java files * Added gitignore within prober * Removed all generated java * 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 * 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 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 * Fixed changes suggested by CydeWeys * 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. * 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 Changes Suggested by jianglai * Fixed Gitignore to take out AutoValue generated code * Removed AutoValue java files * Added gitignore within prober * Removed all generated java * 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 * 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 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 * 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 * Minor Style Fixes * Updated build.gradle file * Updated build.gradle file * Modified license header dates * Modified license header dates * 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 * SpotlessApply run to fix style issues * 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 * 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. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Rebased to master * Updated issues in rebasing * Minor style change on prober/build.gradle * Fixed warnings for java compilation * Fixed files to pass all style tests * Removed outbound message from ActionHandler's fields and renamed Marker Interfaces * 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 Changes Suggested by jianglai * Fixed Gitignore to take out AutoValue generated code * Removed AutoValue java files * Added gitignore within prober * Removed all generated java * 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 * 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 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 * Fixed javadoc for Marker Interfaced * Modified Comments on ActionHandler * Modified Comments on ActionHandler * Removed LocalAddress from Protocol * Removed LocalAddress from Protocol * Fixed Travis Build Issues * 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 * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor Style Fix * Minor Style Fix * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * fixed build issues * fixed build issues * 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 * Modified license header dates * Modified license header dates * 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 * SpotlessApply run to fix style issues * 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 * 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. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Rebased to master * Updated issues in rebasing * Minor style change on prober/build.gradle * Fixed warnings for java compilation * Fixed files to pass all style tests * Fixed changes suggested by CydeWeys * Rebased to Master and added in modified Handlers and ProbingAction * Added missing license headers and JavaDoc * Added missing license headers and JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor fix in NewChannelAction JavaDoc * Minor Style Fix * Minor Style Fix * Full WebWhoIs Sequence Added * Full WebWhoIs Sequence Added * fixed build issues * fixed build issues * 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 * Modified license header dates * Modified license header dates * 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 * SpotlessApply run to fix style issues * 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 * 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. * Removed TestProvider from TestUtils. * Removed TestProvider from TestUtils. * Rebased to master * Updated issues in rebasing * Minor style change on prober/build.gradle * Fixed warnings for java compilation * Fixed files to pass all style tests * Minor syle fixes after succesful rebase onto master
This commit is contained in:
parent
c7478fc52b
commit
27b81ba898
35 changed files with 3545 additions and 73 deletions
|
@ -0,0 +1,162 @@
|
|||
// 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;
|
||||
|
||||
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 com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.handlers.ActionHandler;
|
||||
import google.registry.monitoring.blackbox.handlers.ConversionHandler;
|
||||
import google.registry.monitoring.blackbox.handlers.NettyRule;
|
||||
import google.registry.monitoring.blackbox.handlers.TestActionHandler;
|
||||
import google.registry.monitoring.blackbox.messages.TestMessage;
|
||||
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;
|
||||
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.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ProbingAction} subtypes
|
||||
*
|
||||
* <p>Attempts to test how well each {@link ProbingAction} works with an {@link ActionHandler}
|
||||
* subtype when receiving to all possible types of responses</p>
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ProbingActionTest {
|
||||
|
||||
private static final String TEST_MESSAGE = "MESSAGE_TEST";
|
||||
private static final String SECONDARY_TEST_MESSAGE = "SECONDARY_MESSAGE_TEST";
|
||||
private static final String PROTOCOL_NAME = "TEST_PROTOCOL";
|
||||
private static final String ADDRESS_NAME = "TEST_ADDRESS";
|
||||
private static final int TEST_PORT = 0;
|
||||
|
||||
private static final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
|
||||
/**
|
||||
* Used for testing how well probing step can create connection to blackbox server
|
||||
*/
|
||||
@Rule
|
||||
public NettyRule nettyRule = new NettyRule(eventLoopGroup);
|
||||
/**
|
||||
* We use custom Test {@link ActionHandler} and {@link ConversionHandler} so test depends only on
|
||||
* {@link ProbingAction}
|
||||
*/
|
||||
private ActionHandler testHandler = new TestActionHandler();
|
||||
private ChannelHandler conversionHandler = new ConversionHandler();
|
||||
|
||||
//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 testSuccess_existingChannel() {
|
||||
//setup
|
||||
EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler);
|
||||
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
|
||||
|
||||
// Sets up a Protocol corresponding to when a connection exists.
|
||||
Protocol protocol = Protocol.builder()
|
||||
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
|
||||
.setName(PROTOCOL_NAME)
|
||||
.setPort(TEST_PORT)
|
||||
.setPersistentConnection(true)
|
||||
.build();
|
||||
|
||||
// Sets up a ProbingAction that creates a channel using test specified attributes.
|
||||
ProbingAction action = ProbingAction.builder()
|
||||
.setChannel(channel)
|
||||
.setProtocol(protocol)
|
||||
.setDelay(Duration.ZERO)
|
||||
.setOutboundMessage(new TestMessage(TEST_MESSAGE))
|
||||
.setHost("")
|
||||
.build();
|
||||
|
||||
//tests main function of ProbingAction
|
||||
ChannelFuture future = action.call();
|
||||
|
||||
//Obtains the outboundMessage passed through pipeline after delay
|
||||
Object msg = null;
|
||||
while (msg == null) {
|
||||
msg = channel.readOutbound();
|
||||
}
|
||||
//tests the passed message is exactly what we expect
|
||||
assertThat(msg).isInstanceOf(ByteBuf.class);
|
||||
String request = ((ByteBuf) msg).toString(UTF_8);
|
||||
assertThat(request).isEqualTo(TEST_MESSAGE);
|
||||
|
||||
// Ensures that we haven't marked future as done until response is received.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
//after writing inbound, we should have a success
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(SECONDARY_TEST_MESSAGE.getBytes(US_ASCII)));
|
||||
assertThat(future.isSuccess()).isTrue();
|
||||
|
||||
assertThat(testHandler.toString()).isEqualTo(SECONDARY_TEST_MESSAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_newChannel() throws Exception {
|
||||
//setup
|
||||
|
||||
LocalAddress address = new LocalAddress(ADDRESS_NAME);
|
||||
Bootstrap bootstrap = new Bootstrap()
|
||||
.group(eventLoopGroup)
|
||||
.channel(LocalChannel.class);
|
||||
|
||||
// Sets up a Protocol corresponding to when a new connection is created.
|
||||
Protocol protocol = Protocol.builder()
|
||||
.setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler))
|
||||
.setName(PROTOCOL_NAME)
|
||||
.setPort(TEST_PORT)
|
||||
.setPersistentConnection(false)
|
||||
.build();
|
||||
|
||||
nettyRule.setUpServer(address);
|
||||
|
||||
// Sets up a ProbingAction with existing channel using test specified attributes.
|
||||
ProbingAction action = ProbingAction.builder()
|
||||
.setBootstrap(bootstrap)
|
||||
.setProtocol(protocol)
|
||||
.setDelay(Duration.ZERO)
|
||||
.setOutboundMessage(new TestMessage(TEST_MESSAGE))
|
||||
.setHost(ADDRESS_NAME)
|
||||
.build();
|
||||
|
||||
//tests main function of ProbingAction
|
||||
ChannelFuture future = action.call();
|
||||
|
||||
//Tests to see if message is properly sent to remote server
|
||||
nettyRule.assertReceivedMessage(TEST_MESSAGE);
|
||||
|
||||
future = future.syncUninterruptibly();
|
||||
//Tests to see that, since server responds, we have set future to true
|
||||
assertThat(future.isSuccess()).isTrue();
|
||||
assertThat(((TestActionHandler) testHandler).getResponse().toString()).isEqualTo(TEST_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.monitoring.blackbox;
|
||||
|
||||
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.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import google.registry.monitoring.blackbox.tokens.Token;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class ProbingSequenceTest {
|
||||
|
||||
private ProbingStep firstStep;
|
||||
private ProbingStep secondStep;
|
||||
private ProbingStep thirdStep;
|
||||
|
||||
private Token testToken;
|
||||
|
||||
private ProbingStep setupMockStep() {
|
||||
ProbingStep mock = Mockito.mock(ProbingStep.class);
|
||||
doCallRealMethod().when(mock).nextStep(any(ProbingStep.class));
|
||||
doCallRealMethod().when(mock).nextStep();
|
||||
return mock;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
firstStep = setupMockStep();
|
||||
secondStep = setupMockStep();
|
||||
thirdStep = setupMockStep();
|
||||
|
||||
testToken = Mockito.mock(Token.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSequenceBasicConstruction_Success() {
|
||||
|
||||
ProbingSequence sequence = new ProbingSequence.Builder(testToken)
|
||||
.addStep(firstStep)
|
||||
.addStep(secondStep)
|
||||
.addStep(thirdStep)
|
||||
.build();
|
||||
|
||||
assertThat(firstStep.nextStep()).isEqualTo(secondStep);
|
||||
assertThat(secondStep.nextStep()).isEqualTo(thirdStep);
|
||||
assertThat(thirdStep.nextStep()).isEqualTo(firstStep);
|
||||
|
||||
sequence.start();
|
||||
|
||||
verify(firstStep, times(1)).accept(testToken);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSequenceAdvancedConstruction_Success() {
|
||||
|
||||
ProbingSequence sequence = new ProbingSequence.Builder(testToken)
|
||||
.addStep(thirdStep)
|
||||
.addStep(secondStep)
|
||||
.markFirstRepeated()
|
||||
.addStep(firstStep)
|
||||
.build();
|
||||
|
||||
assertThat(firstStep.nextStep()).isEqualTo(secondStep);
|
||||
assertThat(secondStep.nextStep()).isEqualTo(firstStep);
|
||||
assertThat(thirdStep.nextStep()).isEqualTo(secondStep);
|
||||
|
||||
sequence.start();
|
||||
|
||||
verify(thirdStep, times(1)).accept(testToken);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
// 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;
|
||||
|
||||
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;
|
||||
import google.registry.monitoring.blackbox.handlers.ActionHandler;
|
||||
import google.registry.monitoring.blackbox.handlers.ConversionHandler;
|
||||
import google.registry.monitoring.blackbox.handlers.NettyRule;
|
||||
import google.registry.monitoring.blackbox.handlers.TestActionHandler;
|
||||
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.ChannelHandler;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link ProbingSequence}s and {@link ProbingStep}s and their specific
|
||||
* implementations
|
||||
*/
|
||||
public class ProbingStepTest {
|
||||
|
||||
/**
|
||||
* Basic Constants necessary for tests
|
||||
*/
|
||||
private static final String ADDRESS_NAME = "TEST_ADDRESS";
|
||||
private static final String PROTOCOL_NAME = "TEST_PROTOCOL";
|
||||
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 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
|
||||
*/
|
||||
@Rule
|
||||
public NettyRule nettyRule = new NettyRule(eventLoopGroup);
|
||||
|
||||
|
||||
/**
|
||||
* The two main handlers we need in any test pipeline used that connects to {@link NettyRule's
|
||||
* server}
|
||||
**/
|
||||
private ActionHandler testHandler = new TestActionHandler();
|
||||
private ChannelHandler conversionHandler = new ConversionHandler();
|
||||
|
||||
/**
|
||||
* Creates mock {@link Token} object that returns the host and returns unchanged message when
|
||||
* modifying it.
|
||||
*/
|
||||
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)
|
||||
.modifyMessage(any(OutboundMessageType.class));
|
||||
return token;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewChannel() throws Exception {
|
||||
// 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(false)
|
||||
.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 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);
|
||||
|
||||
//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);
|
||||
|
||||
//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.
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// 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;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
/**
|
||||
* Utility class for various helper methods used in testing.
|
||||
*/
|
||||
public class TestUtils {
|
||||
|
||||
public static FullHttpRequest makeHttpGetRequest(String host, String path) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
||||
request.headers().set("host", host).setInt("content-length", 0);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeHttpResponse(String content, HttpResponseStatus status) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
|
||||
response.headers().setInt("content-length", buf.readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeHttpResponse(HttpResponseStatus status) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
|
||||
response.headers().setInt("content-length", 0);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates HttpResponse given status, redirection location, and other necessary inputs
|
||||
*/
|
||||
public static FullHttpResponse makeRedirectResponse(
|
||||
HttpResponseStatus status, String location, boolean keepAlive) {
|
||||
FullHttpResponse response = makeHttpResponse("", status);
|
||||
response.headers().set("content-type", "text/plain");
|
||||
if (location != null) {
|
||||
response.headers().set("location", location);
|
||||
}
|
||||
if (keepAlive) {
|
||||
response.headers().set("connection", "keep-alive");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// 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.handlers;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import google.registry.monitoring.blackbox.messages.InboundMessageType;
|
||||
import google.registry.monitoring.blackbox.messages.OutboundMessageType;
|
||||
import google.registry.monitoring.blackbox.messages.TestMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
|
||||
/**
|
||||
* {@link ChannelHandler} used in tests to convert {@link OutboundMessageType} to to {@link
|
||||
* ByteBuf}s and convert {@link ByteBuf}s to {@link InboundMessageType}
|
||||
*
|
||||
* <p>Specific type of {@link OutboundMessageType} and {@link InboundMessageType}
|
||||
* used for conversion is the {@link TestMessage} type.</p>
|
||||
*/
|
||||
public class ConversionHandler extends ChannelDuplexHandler {
|
||||
|
||||
/**
|
||||
* Handles inbound conversion
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
ctx.fireChannelRead(new TestMessage(buf.toString(UTF_8)));
|
||||
buf.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles outbound conversion
|
||||
*/
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||
throws Exception {
|
||||
String message = msg.toString();
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(message.getBytes(US_ASCII));
|
||||
super.write(ctx, buf, promise);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
// 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.handlers;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.truth.ThrowableSubject;
|
||||
import google.registry.monitoring.blackbox.ProbingActionTest;
|
||||
import google.registry.monitoring.blackbox.ProbingStepTest;
|
||||
import google.registry.monitoring.blackbox.Protocol;
|
||||
import google.registry.monitoring.blackbox.testservers.TestServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import org.junit.rules.ExternalResource;
|
||||
|
||||
/**
|
||||
* Helper for setting up and testing client / server connection with netty.
|
||||
*
|
||||
* <p>Code based on and almost identical to {@code NettyRule} in the proxy.
|
||||
* Used in {@link SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest}
|
||||
* </p>
|
||||
*/
|
||||
public final class NettyRule extends ExternalResource {
|
||||
|
||||
|
||||
private final EventLoopGroup eventLoopGroup;
|
||||
// Handler attached to server's channel to record the request received.
|
||||
private EchoHandler echoHandler;
|
||||
// Handler attached to client's channel to record the response received.
|
||||
private DumpHandler dumpHandler;
|
||||
private Channel channel;
|
||||
|
||||
// All I/O operations are done inside the single thread within this event loop group, which is
|
||||
// different from the main test thread. Therefore synchronizations are required to make sure that
|
||||
// certain I/O activities are finished when assertions are performed.
|
||||
public NettyRule() {
|
||||
eventLoopGroup = new NioEventLoopGroup(1);
|
||||
}
|
||||
|
||||
public NettyRule(EventLoopGroup e) {
|
||||
eventLoopGroup = e;
|
||||
}
|
||||
|
||||
private static void writeToChannelAndFlush(Channel channel, String data) {
|
||||
ChannelFuture unusedFuture =
|
||||
channel.writeAndFlush(Unpooled.wrappedBuffer(data.getBytes(US_ASCII)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a server channel bound to the given local address.
|
||||
*/
|
||||
public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) {
|
||||
checkState(echoHandler == null, "Can't call setUpServer twice");
|
||||
echoHandler = new EchoHandler();
|
||||
|
||||
new TestServer(eventLoopGroup, localAddress,
|
||||
ImmutableList.<ChannelHandler>builder().add(handlers).add(echoHandler).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a client channel connecting to the give local address.
|
||||
*/
|
||||
void setUpClient(
|
||||
LocalAddress localAddress,
|
||||
Protocol protocol,
|
||||
String host,
|
||||
ChannelHandler handler) {
|
||||
checkState(echoHandler != null, "Must call setUpServer before setUpClient");
|
||||
checkState(dumpHandler == null, "Can't call setUpClient twice");
|
||||
dumpHandler = new DumpHandler();
|
||||
ChannelInitializer<LocalChannel> clientInitializer =
|
||||
new ChannelInitializer<LocalChannel>() {
|
||||
@Override
|
||||
protected void initChannel(LocalChannel ch) throws Exception {
|
||||
// Add the given handler
|
||||
ch.pipeline().addLast(handler);
|
||||
// Add the "dumpHandler" last to log the incoming message
|
||||
ch.pipeline().addLast(dumpHandler);
|
||||
}
|
||||
};
|
||||
Bootstrap b =
|
||||
new Bootstrap()
|
||||
.group(eventLoopGroup)
|
||||
.channel(LocalChannel.class)
|
||||
.handler(clientInitializer)
|
||||
.attr(PROTOCOL_KEY, protocol)
|
||||
.attr(REMOTE_ADDRESS_KEY, host);
|
||||
|
||||
channel = b.connect(localAddress).syncUninterruptibly().channel();
|
||||
}
|
||||
|
||||
private void checkReady() {
|
||||
checkState(channel != null, "Must call setUpClient to finish NettyRule setup");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that custom setup to send message to current server sends right message
|
||||
*/
|
||||
public void assertReceivedMessage(String message) throws Exception {
|
||||
assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a message can go through, both inbound and outbound.
|
||||
*
|
||||
* <p>The client writes the message to the server, which echos it back and saves the string in
|
||||
* its promise. The client receives the echo and saves it in its promise. All these activities
|
||||
* happens in the I/O thread, and this call itself returns immediately.
|
||||
*/
|
||||
void assertThatMessagesWork() throws Exception {
|
||||
checkReady();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
|
||||
writeToChannelAndFlush(channel, "Hello, world!");
|
||||
assertThat(echoHandler.getRequestFuture().get()).isEqualTo("Hello, world!");
|
||||
assertThat(dumpHandler.getResponseFuture().get()).isEqualTo("Hello, world!");
|
||||
}
|
||||
|
||||
Channel getChannel() {
|
||||
checkReady();
|
||||
return channel;
|
||||
}
|
||||
|
||||
ThrowableSubject assertThatServerRootCause() {
|
||||
checkReady();
|
||||
return assertThat(
|
||||
Throwables.getRootCause(
|
||||
assertThrows(ExecutionException.class, () -> echoHandler.getRequestFuture().get())));
|
||||
}
|
||||
|
||||
ThrowableSubject assertThatClientRootCause() {
|
||||
checkReady();
|
||||
return assertThat(
|
||||
Throwables.getRootCause(
|
||||
assertThrows(ExecutionException.class, () -> dumpHandler.getResponseFuture().get())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler that echoes back its inbound message. The message is also saved in a promise for
|
||||
* inspection later.
|
||||
*/
|
||||
public static class EchoHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final CompletableFuture<String> requestFuture = new CompletableFuture<>();
|
||||
|
||||
public Future<String> getRequestFuture() {
|
||||
return requestFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// In the test we only send messages of type ByteBuf.
|
||||
|
||||
assertThat(msg).isInstanceOf(ByteBuf.class);
|
||||
String request = ((ByteBuf) msg).toString(UTF_8);
|
||||
// After the message is written back to the client, fulfill the promise.
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.writeAndFlush(msg).addListener(f -> requestFuture.complete(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves any inbound error as the cause of the promise failure.
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.channel().closeFuture().addListener(f -> requestFuture.completeExceptionally(cause));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler that dumps its inbound message to a promise that can be inspected later.
|
||||
*/
|
||||
private static class DumpHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final CompletableFuture<String> responseFuture = new CompletableFuture<>();
|
||||
|
||||
Future<String> getResponseFuture() {
|
||||
return responseFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// In the test we only send messages of type ByteBuf.
|
||||
assertThat(msg).isInstanceOf(ByteBuf.class);
|
||||
String response = ((ByteBuf) msg).toString(UTF_8);
|
||||
// There is no more use of this message, we should release its reference count so that it
|
||||
// can be more effectively garbage collected by Netty.
|
||||
ReferenceCountUtil.release(msg);
|
||||
// Save the string in the promise and make it as complete.
|
||||
responseFuture.complete(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves any inbound error into the failure cause of the promise.
|
||||
*/
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ctx.channel().closeFuture().addListener(f -> responseFuture.completeExceptionally(cause));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
// 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.handlers;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.getKeyPair;
|
||||
import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.setUpSslChannel;
|
||||
import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.signKeyPair;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.Protocol;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.SniHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertPathBuilderException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.SSLException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SslClientInitializer}.
|
||||
*
|
||||
* <p>To validate that the handler accepts & rejects connections as expected, a test server and a
|
||||
* test client are spun up, and both connect to the {@link LocalAddress} within the JVM. This avoids
|
||||
* the overhead of routing traffic through the network layer, even if it were to go through
|
||||
* loopback. It also alleviates the need to pick a free port to use.
|
||||
*
|
||||
* <p>The local addresses used in each test method must to be different, otherwise tests run in
|
||||
* parallel may interfere with each other.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class SslClientInitializerTest {
|
||||
|
||||
/**
|
||||
* Fake host to test if the SSL engine gets the correct peer host.
|
||||
*/
|
||||
private static final String SSL_HOST = "www.example.tld";
|
||||
|
||||
/**
|
||||
* Fake port to test if the SSL engine gets the correct peer port.
|
||||
*/
|
||||
private static final int SSL_PORT = 12345;
|
||||
/**
|
||||
* Fake protocol saved in channel attribute.
|
||||
*/
|
||||
private static final Protocol PROTOCOL = Protocol.builder()
|
||||
.setName("ssl")
|
||||
.setPort(SSL_PORT)
|
||||
.setHandlerProviders(ImmutableList.of())
|
||||
.setPersistentConnection(false)
|
||||
.build();
|
||||
@Rule
|
||||
public NettyRule nettyRule = new NettyRule();
|
||||
@Parameter(0)
|
||||
public SslProvider sslProvider;
|
||||
/**
|
||||
* Saves the SNI hostname received by the server, if sent by the client.
|
||||
*/
|
||||
private String sniHostReceived;
|
||||
|
||||
// We do our best effort to test all available SSL providers.
|
||||
@Parameters(name = "{0}")
|
||||
public static SslProvider[] data() {
|
||||
return OpenSsl.isAvailable()
|
||||
? new SslProvider[]{SslProvider.JDK, SslProvider.OPENSSL}
|
||||
: new SslProvider[]{SslProvider.JDK};
|
||||
}
|
||||
|
||||
private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate)
|
||||
throws Exception {
|
||||
SslContext sslContext = SslContextBuilder.forServer(privateKey, certificate).build();
|
||||
return new SniHandler(
|
||||
hostname -> {
|
||||
sniHostReceived = hostname;
|
||||
return sslContext;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_swappedInitializerWithSslHandler() throws Exception {
|
||||
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider);
|
||||
EmbeddedChannel channel = new EmbeddedChannel();
|
||||
channel.attr(PROTOCOL_KEY).set(PROTOCOL);
|
||||
channel.attr(REMOTE_ADDRESS_KEY).set(SSL_HOST);
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(sslClientInitializer);
|
||||
ChannelHandler firstHandler = pipeline.first();
|
||||
assertThat(firstHandler.getClass()).isEqualTo(SslHandler.class);
|
||||
SslHandler sslHandler = (SslHandler) firstHandler;
|
||||
assertThat(sslHandler.engine().getPeerHost()).isEqualTo(SSL_HOST);
|
||||
assertThat(sslHandler.engine().getPeerPort()).isEqualTo(SSL_PORT);
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_protocolAttributeNotSet() {
|
||||
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider);
|
||||
EmbeddedChannel channel = new EmbeddedChannel();
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(sslClientInitializer);
|
||||
// Channel initializer swallows error thrown, and closes the connection.
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure_defaultTrustManager_rejectSelfSignedCert() throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate(SSL_HOST);
|
||||
LocalAddress localAddress =
|
||||
new LocalAddress("DEFAULT_TRUST_MANAGER_REJECT_SELF_SIGNED_CERT_" + sslProvider);
|
||||
nettyRule.setUpServer(localAddress, getServerHandler(ssc.key(), ssc.cert()));
|
||||
SslClientInitializer<LocalChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider);
|
||||
|
||||
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
|
||||
// The connection is now terminated, both the client side and the server side should get
|
||||
// exceptions.
|
||||
nettyRule.assertThatClientRootCause().isInstanceOf(CertPathBuilderException.class);
|
||||
nettyRule.assertThatServerRootCause().isInstanceOf(SSLException.class);
|
||||
assertThat(nettyRule.getChannel().isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_customTrustManager_acceptCertSignedByTrustedCa() throws Exception {
|
||||
LocalAddress localAddress =
|
||||
new LocalAddress("CUSTOM_TRUST_MANAGER_ACCEPT_CERT_SIGNED_BY_TRUSTED_CA_" + sslProvider);
|
||||
|
||||
// Generate a new key pair.
|
||||
KeyPair keyPair = getKeyPair();
|
||||
|
||||
// Generate a self signed certificate, and use it to sign the key pair.
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
X509Certificate cert = signKeyPair(ssc, keyPair, SSL_HOST);
|
||||
|
||||
// Set up the server to use the signed cert and private key to perform handshake;
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
|
||||
|
||||
// Set up the client to trust the self signed cert used to sign the cert that server provides.
|
||||
SslClientInitializer<LocalChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider, new X509Certificate[]{ssc.cert()});
|
||||
|
||||
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
|
||||
|
||||
setUpSslChannel(nettyRule.getChannel(), cert);
|
||||
nettyRule.assertThatMessagesWork();
|
||||
|
||||
// Verify that the SNI extension is sent during handshake.
|
||||
assertThat(sniHostReceived).isEqualTo(SSL_HOST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure_customTrustManager_wrongHostnameInCertificate() throws Exception {
|
||||
LocalAddress localAddress =
|
||||
new LocalAddress("CUSTOM_TRUST_MANAGER_WRONG_HOSTNAME_" + sslProvider);
|
||||
|
||||
// Generate a new key pair.
|
||||
KeyPair keyPair = getKeyPair();
|
||||
|
||||
// Generate a self signed certificate, and use it to sign the key pair.
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
X509Certificate cert = signKeyPair(ssc, keyPair, "wrong.com");
|
||||
|
||||
// Set up the server to use the signed cert and private key to perform handshake;
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
|
||||
|
||||
// Set up the client to trust the self signed cert used to sign the cert that server provides.
|
||||
SslClientInitializer<LocalChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider, new X509Certificate[]{ssc.cert()});
|
||||
|
||||
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
|
||||
|
||||
// When the client rejects the server cert due to wrong hostname, both the client and server
|
||||
// should throw exceptions.
|
||||
nettyRule.assertThatClientRootCause().isInstanceOf(CertificateException.class);
|
||||
nettyRule.assertThatClientRootCause().hasMessageThat().contains(SSL_HOST);
|
||||
nettyRule.assertThatServerRootCause().isInstanceOf(SSLException.class);
|
||||
assertThat(nettyRule.getChannel().isActive()).isFalse();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// 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.handlers;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.x509.X509V3CertificateGenerator;
|
||||
|
||||
/**
|
||||
* Utility class that provides methods used by {@link SslClientInitializerTest}
|
||||
*/
|
||||
public class SslInitializerTestUtils {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public static KeyPair getKeyPair() throws Exception {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||
keyPairGenerator.initialize(2048, new SecureRandom());
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the given key pair with the given self signed certificate.
|
||||
*
|
||||
* @return signed public key (of the key pair) certificate
|
||||
*/
|
||||
public static X509Certificate signKeyPair(
|
||||
SelfSignedCertificate ssc, KeyPair keyPair, String hostname) throws Exception {
|
||||
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
|
||||
X500Principal dnName = new X500Principal("CN=" + hostname);
|
||||
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
|
||||
certGen.setSubjectDN(dnName);
|
||||
certGen.setIssuerDN(ssc.cert().getSubjectX500Principal());
|
||||
certGen.setNotBefore(Date.from(Instant.now().minus(Duration.ofDays(1))));
|
||||
certGen.setNotAfter(Date.from(Instant.now().plus(Duration.ofDays(1))));
|
||||
certGen.setPublicKey(keyPair.getPublic());
|
||||
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
|
||||
return certGen.generate(ssc.key(), "BC");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies tha the SSL channel is established as expected, and also sends a message to the server
|
||||
* and verifies if it is echoed back correctly.
|
||||
*
|
||||
* @param certs The certificate that the server should provide.
|
||||
* @return The SSL session in current channel, can be used for further validation.
|
||||
*/
|
||||
static SSLSession setUpSslChannel(
|
||||
Channel channel,
|
||||
X509Certificate... certs)
|
||||
throws Exception {
|
||||
SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
|
||||
// Wait till the handshake is complete.
|
||||
sslHandler.handshakeFuture().get();
|
||||
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
assertThat(sslHandler.handshakeFuture().isSuccess()).isTrue();
|
||||
assertThat(sslHandler.engine().getSession().isValid()).isTrue();
|
||||
assertThat(sslHandler.engine().getSession().getPeerCertificates())
|
||||
.asList()
|
||||
.containsExactlyElementsIn(certs);
|
||||
// Returns the SSL session for further assertion.
|
||||
return sslHandler.engine().getSession();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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.handlers;
|
||||
|
||||
import google.registry.monitoring.blackbox.exceptions.FailureException;
|
||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||
import google.registry.monitoring.blackbox.messages.InboundMessageType;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
* Concrete implementation of {@link ActionHandler} that does nothing different from parent class
|
||||
* other than store and return the {@code inboundMessage}
|
||||
*/
|
||||
public class TestActionHandler extends ActionHandler {
|
||||
|
||||
private InboundMessageType receivedMessage;
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, InboundMessageType inboundMessage)
|
||||
throws FailureException, UndeterminedStateException {
|
||||
receivedMessage = inboundMessage;
|
||||
super.channelRead0(ctx, inboundMessage);
|
||||
}
|
||||
|
||||
public InboundMessageType getResponse() {
|
||||
return receivedMessage;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
// 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.handlers;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY;
|
||||
import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.monitoring.blackbox.TestUtils.makeHttpGetRequest;
|
||||
import static google.registry.monitoring.blackbox.TestUtils.makeHttpResponse;
|
||||
import static google.registry.monitoring.blackbox.TestUtils.makeRedirectResponse;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.Protocol;
|
||||
import google.registry.monitoring.blackbox.exceptions.FailureException;
|
||||
import google.registry.monitoring.blackbox.messages.HttpRequestMessage;
|
||||
import google.registry.monitoring.blackbox.messages.HttpResponseMessage;
|
||||
import google.registry.monitoring.blackbox.testservers.TestServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import javax.inject.Provider;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WebWhoisActionHandler}.
|
||||
*
|
||||
* <p>Attempts to test how well {@link WebWhoisActionHandler} works
|
||||
* when responding to all possible types of responses </p>
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class WebWhoisActionHandlerTest {
|
||||
|
||||
private static final int HTTP_PORT = 80;
|
||||
private static final String HTTP_REDIRECT = "http://";
|
||||
private static final String TARGET_HOST = "whois.nic.tld";
|
||||
private static final String DUMMY_URL = "__WILL_NOT_WORK__";
|
||||
private final Protocol standardProtocol = Protocol.builder()
|
||||
.setHandlerProviders(ImmutableList.of(() -> new WebWhoisActionHandler(
|
||||
null, null, null, null)))
|
||||
.setName("http")
|
||||
.setPersistentConnection(false)
|
||||
.setPort(HTTP_PORT)
|
||||
.build();
|
||||
|
||||
|
||||
private EmbeddedChannel channel;
|
||||
private ActionHandler actionHandler;
|
||||
private Provider<? extends ChannelHandler> actionHandlerProvider;
|
||||
private Protocol initialProtocol;
|
||||
private HttpRequestMessage msg;
|
||||
|
||||
|
||||
/**
|
||||
* Creates default protocol with empty list of handlers and specified other inputs
|
||||
*/
|
||||
private Protocol createProtocol(String name, int port, boolean persistentConnection) {
|
||||
return Protocol.builder()
|
||||
.setName(name)
|
||||
.setPort(port)
|
||||
.setHandlerProviders(ImmutableList.of(actionHandlerProvider))
|
||||
.setPersistentConnection(persistentConnection)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes new WebWhoisActionHandler
|
||||
*/
|
||||
private void setupActionHandler(Bootstrap bootstrap, HttpRequestMessage messageTemplate) {
|
||||
actionHandler = new WebWhoisActionHandler(
|
||||
bootstrap,
|
||||
standardProtocol,
|
||||
standardProtocol,
|
||||
messageTemplate
|
||||
);
|
||||
actionHandlerProvider = () -> actionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up testing channel with requisite attributes
|
||||
*/
|
||||
private void setupChannel(Protocol protocol) {
|
||||
channel = new EmbeddedChannel(actionHandler);
|
||||
channel.attr(PROTOCOL_KEY).set(protocol);
|
||||
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
|
||||
}
|
||||
|
||||
private Bootstrap makeBootstrap(EventLoopGroup group) {
|
||||
return new Bootstrap()
|
||||
.group(group)
|
||||
.channel(LocalChannel.class);
|
||||
}
|
||||
|
||||
|
||||
private void setup(String hostName, Bootstrap bootstrap, boolean persistentConnection) {
|
||||
msg = new HttpRequestMessage(makeHttpGetRequest(hostName, ""));
|
||||
setupActionHandler(bootstrap, msg);
|
||||
initialProtocol = createProtocol("testProtocol", 0, persistentConnection);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasic_responseOk() {
|
||||
//setup
|
||||
setup("", null, true);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
//stores future
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
FullHttpResponse response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
|
||||
|
||||
//assesses that future listener isn't triggered yet.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
//assesses that we successfully received good response and protocol is unchanged
|
||||
assertThat(future.isSuccess()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasic_responseFailure_badRequest() {
|
||||
//setup
|
||||
setup("", null, false);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
// Stores future that informs when action is completed.
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
FullHttpResponse response = new HttpResponseMessage(
|
||||
makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
|
||||
|
||||
// Assesses that future listener isn't triggered yet.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
// Assesses that listener is triggered, but event is not success
|
||||
assertThat(future.isDone()).isTrue();
|
||||
assertThat(future.isSuccess()).isFalse();
|
||||
|
||||
// Ensures that we fail as a result of a FailureException.
|
||||
assertThat(future.cause() instanceof FailureException).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasic_responseFailure_badURL() {
|
||||
//setup
|
||||
setup("", null, false);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
//stores future
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
FullHttpResponse response = new HttpResponseMessage(
|
||||
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true));
|
||||
|
||||
//assesses that future listener isn't triggered yet.
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
//assesses that listener is triggered, and event is success
|
||||
assertThat(future.isDone()).isTrue();
|
||||
assertThat(future.isSuccess()).isFalse();
|
||||
|
||||
// Ensures that we fail as a result of a FailureException.
|
||||
assertThat(future.cause() instanceof FailureException).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdvanced_redirect() {
|
||||
// Sets up EventLoopGroup with 1 thread to be blocking.
|
||||
EventLoopGroup group = new NioEventLoopGroup(1);
|
||||
|
||||
// Sets up embedded channel.
|
||||
setup("", makeBootstrap(group), false);
|
||||
setupChannel(initialProtocol);
|
||||
|
||||
// Initializes LocalAddress with unique String.
|
||||
LocalAddress address = new LocalAddress(TARGET_HOST);
|
||||
|
||||
//stores future
|
||||
ChannelFuture future = actionHandler.getFinishedFuture();
|
||||
channel.writeOutbound(msg);
|
||||
|
||||
// Path that we test WebWhoisActionHandler uses.
|
||||
String path = "/test";
|
||||
|
||||
// Sets up the local server that the handler will be redirected to.
|
||||
TestServer.webWhoisServer(group, address, "", TARGET_HOST, path);
|
||||
|
||||
FullHttpResponse response =
|
||||
new HttpResponseMessage(makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY,
|
||||
HTTP_REDIRECT + TARGET_HOST + path, true));
|
||||
|
||||
//checks that future has not been set to successful or a failure
|
||||
assertThat(future.isDone()).isFalse();
|
||||
|
||||
channel.writeInbound(response);
|
||||
|
||||
//makes sure old channel is shut down when attempting redirection
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
|
||||
//assesses that we successfully received good response and protocol is unchanged
|
||||
assertThat(future.syncUninterruptibly().isSuccess()).isTrue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// 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.messages;
|
||||
|
||||
import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException;
|
||||
|
||||
/**
|
||||
* {@link InboundMessageType} and {@link OutboundMessageType} type for the purpose of containing
|
||||
* String messages to be passed down channel
|
||||
*/
|
||||
public class TestMessage implements OutboundMessageType, InboundMessageType {
|
||||
|
||||
private String message;
|
||||
|
||||
public TestMessage(String msg) {
|
||||
message = msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutboundMessageType modifyMessage(String... args) throws UndeterminedStateException {
|
||||
message = args[0];
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// 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.testservers;
|
||||
|
||||
import static google.registry.monitoring.blackbox.TestUtils.makeHttpResponse;
|
||||
import static google.registry.monitoring.blackbox.TestUtils.makeRedirectResponse;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.messages.HttpResponseMessage;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalServerChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
/**
|
||||
* Mock Server Superclass whose subclasses implement specific behaviors we expect blackbox server to
|
||||
* perform
|
||||
*/
|
||||
public class TestServer {
|
||||
|
||||
public TestServer(LocalAddress localAddress, ImmutableList<? extends ChannelHandler> handlers) {
|
||||
this(new NioEventLoopGroup(1), localAddress, handlers);
|
||||
}
|
||||
|
||||
public TestServer(EventLoopGroup eventLoopGroup, LocalAddress localAddress,
|
||||
ImmutableList<? extends ChannelHandler> handlers) {
|
||||
//Creates ChannelInitializer with handlers specified
|
||||
ChannelInitializer<LocalChannel> serverInitializer = new ChannelInitializer<LocalChannel>() {
|
||||
@Override
|
||||
protected void initChannel(LocalChannel ch) {
|
||||
for (ChannelHandler handler : handlers) {
|
||||
ch.pipeline().addLast(handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
//Sets up serverBootstrap with specified initializer, eventLoopGroup, and using
|
||||
// LocalServerChannel class
|
||||
ServerBootstrap serverBootstrap =
|
||||
new ServerBootstrap()
|
||||
.group(eventLoopGroup)
|
||||
.channel(LocalServerChannel.class)
|
||||
.childHandler(serverInitializer);
|
||||
|
||||
ChannelFuture unusedFuture = serverBootstrap.bind(localAddress).syncUninterruptibly();
|
||||
|
||||
}
|
||||
|
||||
public static TestServer webWhoisServer(EventLoopGroup eventLoopGroup,
|
||||
LocalAddress localAddress, String redirectInput, String destinationInput,
|
||||
String destinationPath) {
|
||||
return new TestServer(
|
||||
eventLoopGroup,
|
||||
localAddress,
|
||||
ImmutableList.of(new RedirectHandler(redirectInput, destinationInput, destinationPath))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler that will wither redirect client, give successful response, or give error messge
|
||||
*/
|
||||
@Sharable
|
||||
static class RedirectHandler extends SimpleChannelInboundHandler<HttpRequest> {
|
||||
|
||||
private String redirectInput;
|
||||
private String destinationInput;
|
||||
private String destinationPath;
|
||||
|
||||
/**
|
||||
* @param redirectInput - Server will send back redirect to {@code destinationInput} when
|
||||
* receiving a request with this host location
|
||||
* @param destinationInput - Server will send back an {@link HttpResponseStatus} OK response
|
||||
* when receiving a request with this host location
|
||||
*/
|
||||
public RedirectHandler(String redirectInput, String destinationInput, String destinationPath) {
|
||||
this.redirectInput = redirectInput;
|
||||
this.destinationInput = destinationInput;
|
||||
this.destinationPath = destinationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads input {@link HttpRequest}, and creates appropriate {@link HttpResponseMessage} based on
|
||||
* what header location is
|
||||
*/
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {
|
||||
HttpResponse response;
|
||||
if (request.headers().get("host").equals(redirectInput)) {
|
||||
response = new HttpResponseMessage(
|
||||
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, destinationInput, true));
|
||||
} else if (request.headers().get("host").equals(destinationInput)
|
||||
&& request.uri().equals(destinationPath)) {
|
||||
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
|
||||
} else {
|
||||
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
|
||||
}
|
||||
ChannelFuture unusedFuture = ctx.channel().writeAndFlush(response);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// 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.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 org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link WebWhoisToken}
|
||||
*/
|
||||
@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);
|
||||
|
||||
public Token webToken = new WebWhoisToken(TEST_DOMAINS);
|
||||
|
||||
@Test
|
||||
public void testMessageModification() throws UndeterminedStateException {
|
||||
//creates Request message with header
|
||||
HttpRequestMessage message = new HttpRequestMessage();
|
||||
message.headers().set("host", HOST);
|
||||
|
||||
//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));
|
||||
}
|
||||
|
||||
/**
|
||||
* As Circular Linked List has not been implemented yet, we cannot yet wrap around, so we don't
|
||||
* test that in testing {@code next}.
|
||||
*/
|
||||
@Test
|
||||
public void testNextToken() {
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
|
||||
webToken = webToken.next();
|
||||
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + SECOND_TLD);
|
||||
webToken = webToken.next();
|
||||
|
||||
assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue