/*
* Copyright 2013 Basho Technologies Inc.
*
* 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 com.basho.riak.client.core;
import com.basho.riak.client.core.RiakNode.State;
import com.google.protobuf.Message;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import java.net.UnknownHostException;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.jayway.awaitility.Awaitility.await;
import static com.jayway.awaitility.Awaitility.fieldIn;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Brian Roach <roach at basho dot com>
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bootstrap.class, FutureOperation.class, RiakMessage.class})
public class RiakNodeTest
{
@Test
public void builderProducesDefaultNode() throws UnknownHostException
{
RiakNode node = new RiakNode.Builder().build();
assertEquals(node.getRemoteAddress(), RiakNode.Builder.DEFAULT_REMOTE_ADDRESS);
assertEquals(node.getPort(), RiakNode.Builder.DEFAULT_REMOTE_PORT);
assertEquals(node.getNodeState(), State.CREATED);
assertEquals(node.getMaxConnections(), Integer.MAX_VALUE);
assertEquals(node.getConnectionTimeout(), RiakNode.Builder.DEFAULT_CONNECTION_TIMEOUT);
assertEquals(node.getIdleTimeout(), RiakNode.Builder.DEFAULT_IDLE_TIMEOUT);
assertEquals(node.getMinConnections(), RiakNode.Builder.DEFAULT_MIN_CONNECTIONS);
assertEquals(node.availablePermits(), Integer.MAX_VALUE);
}
@Test
public void builderProducesCorrectNode() throws UnknownHostException
{
final int IDLE_TIMEOUT = 2000;
final int CONNECTION_TIMEOUT = 2001;
final int MIN_CONNECTIONS = 2002;
final int MAX_CONNECTIONS = 2003;
final int PORT = 2004;
final int READ_TIMEOUT = 2005;
final String REMOTE_ADDRESS = "localhost";
final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
final Bootstrap BOOTSTRAP = PowerMockito.spy(new Bootstrap());
doReturn(BOOTSTRAP).when(BOOTSTRAP).clone();
RiakNode node = new RiakNode.Builder()
.withIdleTimeout(IDLE_TIMEOUT)
.withConnectionTimeout(CONNECTION_TIMEOUT)
.withMinConnections(MIN_CONNECTIONS)
.withMaxConnections(MAX_CONNECTIONS)
.withRemotePort(PORT)
.withRemoteAddress(REMOTE_ADDRESS)
.withExecutor(EXECUTOR)
.withBootstrap(BOOTSTRAP)
.build();
assertEquals(node.getRemoteAddress(), REMOTE_ADDRESS);
assertEquals(node.getNodeState(), RiakNode.State.CREATED);
assertEquals(node.getMaxConnections(), MAX_CONNECTIONS);
assertEquals(node.getConnectionTimeout(), CONNECTION_TIMEOUT);
assertEquals(node.getIdleTimeout(), IDLE_TIMEOUT);
assertEquals(node.getMinConnections(), MIN_CONNECTIONS);
assertEquals(node.getRemoteAddress(), REMOTE_ADDRESS);
assertEquals(node.availablePermits(), MAX_CONNECTIONS);
assertEquals(node.getPort(), PORT);
}
@Test
public void nodeRegistersListeners() throws UnknownHostException
{
RiakNode node = new RiakNode.Builder().build();
NodeStateListener listener = mock(NodeStateListener.class);
node.addStateListener(listener);
boolean removed = node.removeStateListener(listener);
assertTrue(removed);
}
@Test
public void nodeNotifiesListeners() throws UnknownHostException, Exception
{
RiakNode node = new RiakNode.Builder().build();
NodeStateListener listener = mock(NodeStateListener.class);
node.addStateListener(listener);
Whitebox.invokeMethod(node, "notifyStateListeners", new Object[0]);
verify(listener).nodeStateChanged(node, RiakNode.State.CREATED);
}
@Test
public void nodeStartsMinConnections() throws InterruptedException, UnknownHostException
{
final int MIN_CONNECTIONS = 5;
ChannelFuture future = mock(ChannelFuture.class);
Channel c = mock(Channel.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(c).closeFuture();
doReturn(true).when(c).isOpen();
doReturn(future).when(future).await();
doReturn(true).when(future).isSuccess();
doReturn(c).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder()
.withBootstrap(bootstrap)
.withMinConnections(MIN_CONNECTIONS)
.build();
node.start();
Deque<?> available = Whitebox.getInternalState(node, "available");
assertEquals(MIN_CONNECTIONS, available.size());
assertEquals(node.getNodeState(), State.RUNNING);
}
@Test
public void NodeRespectsMax() throws InterruptedException, UnknownHostException, Exception
{
final int MAX_CONNECTIONS = 2;
ChannelFuture future = mock(ChannelFuture.class);
Channel c = mock(Channel.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(c).closeFuture();
doReturn(true).when(c).isOpen();
doReturn(future).when(future).await();
doReturn(true).when(future).isSuccess();
doReturn(c).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder()
.withBootstrap(bootstrap)
.withMaxConnections(MAX_CONNECTIONS)
.build();
node.start();
for (int i = 0; i < MAX_CONNECTIONS; i++)
{
assertNotNull(Whitebox.invokeMethod(node, "getConnection", new Object[0]));
}
assertNull(Whitebox.invokeMethod(node, "getConnection", new Object[0]));
assertEquals(0, node.availablePermits());
node.setMaxConnections(MAX_CONNECTIONS + 1);
assertNotNull(Whitebox.invokeMethod(node, "getConnection", new Object[0]));
assertEquals(0, node.availablePermits());
}
@Test
public void channelsReturnedCorrectly() throws InterruptedException, UnknownHostException, Exception
{
final int MAX_CONNECTIONS = 1;
ChannelFuture future = mock(ChannelFuture.class);
Channel c = mock(Channel.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(c).closeFuture();
doReturn(true).when(c).isOpen();
doReturn(future).when(future).await();
doReturn(true).when(future).isSuccess();
doReturn(c).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder()
.withBootstrap(bootstrap)
.withMaxConnections(MAX_CONNECTIONS)
.build();
node.start();
assertNotNull(Whitebox.invokeMethod(node, "getConnection", new Object[0]));
assertNull(Whitebox.invokeMethod(node, "getConnection", new Object[0]));
Whitebox.invokeMethod(node, "returnConnection", c);
Deque<?> available = Whitebox.getInternalState(node, "available");
assertEquals(1, available.size());
assertNotNull(Whitebox.invokeMethod(node, "getConnection", new Object[0]));
}
@Test
public void healthCheckChangesState()
throws InterruptedException, UnknownHostException, Exception
{
ChannelFuture future = mock(ChannelFuture.class);
Channel c = mock(Channel.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(c).closeFuture();
doReturn(true).when(c).isOpen();
doReturn(future).when(future).await();
doReturn(false).when(future).isSuccess();
doReturn(c).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder()
.withBootstrap(bootstrap)
.build();
for (int i = 0; i < 5; i++)
{
ChannelFutureListener listener = Whitebox.getInternalState(node, "inAvailableCloseListener", RiakNode.class);
listener.operationComplete(future);
}
NodeStateListener listener = mock(NodeStateListener.class);
node.addStateListener(listener);
Whitebox.setInternalState(node, "state", State.RUNNING);
Whitebox.invokeMethod(node, "checkHealth", new Object[0]);
verify(listener).nodeStateChanged(node, State.HEALTH_CHECKING);
}
@Test
public void idleReaperTest() throws InterruptedException, UnknownHostException, Exception
{
ChannelFuture future = mock(ChannelFuture.class);
Channel c = mock(Channel.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(c).closeFuture();
doReturn(true).when(c).isOpen();
doReturn(future).when(future).await();
doReturn(true).when(future).isSuccess();
doReturn(c).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder()
.withBootstrap(bootstrap)
.withMinConnections(1)
.withIdleTimeout(1)
.build();
node.start();
Channel[] channelArray = new Channel[6];
for (int i = 0; i < 6; i++)
{
channelArray[i] = Whitebox.invokeMethod(node, "getConnection", new Object[0]);
assertNotNull(channelArray[i]);
}
for (Channel channel : channelArray)
{
Whitebox.invokeMethod(node, "returnConnection", channel);
}
Deque<?> available = Whitebox.getInternalState(node, "available");
assertEquals(6, available.size());
Thread.sleep(10);
Whitebox.invokeMethod(node, "reapIdleConnections", new Object[0]);
assertEquals(1, available.size());
}
@Test
public void nodeExecutesOperation() throws InterruptedException, UnknownHostException
{
Channel channel = mock(Channel.class);
ChannelPipeline channelPipeline = mock(ChannelPipeline.class);
ChannelFuture future = mock(ChannelFuture.class);
FutureOperation operation = PowerMockito.spy(new FutureOperationImpl());
RiakMessage response = PowerMockito.mock(RiakMessage.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(channel).closeFuture();
doReturn(true).when(channel).isOpen();
doReturn(channelPipeline).when(channel).pipeline();
doReturn(future).when(channel).writeAndFlush(operation);
doReturn(future).when(future).await();
doReturn(true).when(future).isSuccess();
doReturn(channel).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder().withBootstrap(bootstrap).build();
node.start();
boolean accepted = node.execute(operation);
assertTrue(accepted);
verify(channel).writeAndFlush(operation);
verify(operation).setLastNode(node);
assertEquals(1, node.getNumInProgress());
node.onSuccess(channel, response);
assertEquals(0, node.getNumInProgress());
verify(operation).isDone();
}
@Test
public void nodeFailsOperation() throws InterruptedException, UnknownHostException
{
Channel channel = mock(Channel.class);
ChannelPipeline channelPipeline = mock(ChannelPipeline.class);
ChannelFuture future = mock(ChannelFuture.class);
FutureOperation operation = PowerMockito.spy(new FutureOperationImpl());
Throwable t = mock(Throwable.class);
Bootstrap bootstrap = PowerMockito.spy(new Bootstrap());
doReturn(future).when(channel).closeFuture();
doReturn(true).when(channel).isOpen();
doReturn(channelPipeline).when(channel).pipeline();
doReturn(future).when(channel).writeAndFlush(operation);
doReturn(future).when(future).await();
doReturn(true).when(future).isSuccess();
doReturn(channel).when(future).channel();
doReturn(future).when(bootstrap).connect();
doReturn(bootstrap).when(bootstrap).clone();
RiakNode node = new RiakNode.Builder().withBootstrap(bootstrap).build();
node.start();
boolean accepted = node.execute(operation);
assertTrue(accepted);
verify(channel).writeAndFlush(operation);
verify(operation).setLastNode(node);
Map<?, ?> inProgressMap = Whitebox.getInternalState(node, "inProgressMap");
assertEquals(1, inProgressMap.size());
node.onException(channel, t);
await().atMost(500, TimeUnit.MILLISECONDS).until(fieldIn(operation).ofType(Throwable.class).andWithName("exception"), equalTo(t));
}
private class FutureOperationImpl extends FutureOperation<String, Message, Void>
{
@Override
protected String convert(List<Message> rawResponse)
{
return "value";
}
@Override
protected Message decode(RiakMessage rawMessage)
{
return null;
}
@Override
protected RiakMessage createChannelMessage()
{
return new RiakMessage((byte) 0, new byte[0]);
}
@Override
public Void getQueryInfo()
{
return null;
}
}
}