/*
* Copyright Terracotta, 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 org.ehcache.clustered.server.store;
import org.ehcache.clustered.common.Consistency;
import org.ehcache.clustered.common.PoolAllocation;
import org.ehcache.clustered.common.PoolAllocation.Dedicated;
import org.ehcache.clustered.common.PoolAllocation.Shared;
import org.ehcache.clustered.common.ServerSideConfiguration;
import org.ehcache.clustered.common.internal.ServerStoreConfiguration;
import org.ehcache.clustered.common.internal.exceptions.InvalidServerStoreConfigurationException;
import org.ehcache.clustered.common.internal.exceptions.LifecycleException;
import org.ehcache.clustered.common.internal.messages.ConcurrentEntityMessage;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityMessage;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponseFactory;
import org.ehcache.clustered.common.internal.messages.EhcacheResponseType;
import org.ehcache.clustered.common.internal.messages.LifeCycleMessageFactory;
import org.ehcache.clustered.common.internal.messages.ServerStoreMessageFactory;
import org.ehcache.clustered.common.internal.messages.ServerStoreOpMessage;
import org.ehcache.clustered.common.internal.store.ClusterTierEntityConfiguration;
import org.ehcache.clustered.server.ConcurrencyStrategies;
import org.ehcache.clustered.server.EhcacheStateServiceImpl;
import org.ehcache.clustered.server.KeySegmentMapper;
import org.ehcache.clustered.server.ServerSideServerStore;
import org.ehcache.clustered.server.ServerStoreEvictionListener;
import org.ehcache.clustered.server.internal.messages.EhcacheDataSyncMessage;
import org.ehcache.clustered.server.internal.messages.PassiveReplicationMessage;
import org.ehcache.clustered.server.state.ClientMessageTracker;
import org.ehcache.clustered.server.state.EhcacheStateService;
import org.ehcache.clustered.server.state.InvalidationTracker;
import org.ehcache.clustered.server.state.config.EhcacheStoreStateServiceConfig;
import org.ehcache.clustered.server.store.ClusterTierActiveEntity.InvalidationHolder;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.terracotta.entity.ClientCommunicator;
import org.terracotta.entity.ClientDescriptor;
import org.terracotta.entity.ConfigurationException;
import org.terracotta.entity.IEntityMessenger;
import org.terracotta.entity.PassiveSynchronizationChannel;
import org.terracotta.entity.ServiceConfiguration;
import org.terracotta.entity.ServiceRegistry;
import org.terracotta.management.service.monitoring.ActiveEntityMonitoringServiceConfiguration;
import org.terracotta.management.service.monitoring.ConsumerManagementRegistryConfiguration;
import org.terracotta.management.service.monitoring.EntityMonitoringService;
import org.terracotta.offheapresource.OffHeapResource;
import org.terracotta.offheapresource.OffHeapResourceIdentifier;
import org.terracotta.offheapresource.OffHeapResources;
import org.terracotta.offheapstore.util.MemoryUnit;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import static org.ehcache.clustered.common.internal.store.Util.createPayload;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
public class ClusterTierActiveEntityTest {
private static final LifeCycleMessageFactory MESSAGE_FACTORY = new LifeCycleMessageFactory();
private static final UUID CLIENT_ID = UUID.randomUUID();
private static final KeySegmentMapper DEFAULT_MAPPER = new KeySegmentMapper(16);
private String defaultStoreName = "store";
private String defaultResource = "default";
private String defaultSharedPool = "defaultShared";
private String identifier = "identifier";
private OffHeapIdentifierRegistry defaultRegistry;
private ServerStoreConfiguration defaultStoreConfiguration;
private ClusterTierEntityConfiguration defaultConfiguration;
@Before
public void setUp() {
MESSAGE_FACTORY.setClientId(CLIENT_ID);
defaultRegistry = new OffHeapIdentifierRegistry();
defaultRegistry.addResource(defaultResource, 10, MemoryUnit.MEGABYTES);
defaultStoreConfiguration = new ServerStoreConfigBuilder().dedicated(defaultResource, 1024, MemoryUnit.KILOBYTES).build();
defaultConfiguration = new ClusterTierEntityConfiguration(identifier, defaultStoreName,
defaultStoreConfiguration);
}
@Test(expected = ConfigurationException.class)
public void testConfigNull() throws Exception {
new ClusterTierActiveEntity(mock(ServiceRegistry.class), null, DEFAULT_MAPPER);
}
@Test
public void testConnected() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
Set<ClientDescriptor> connectedClients = activeEntity.getConnectedClients();
assertThat(connectedClients, hasSize(1));
assertThat(connectedClients, hasItem(client));
}
@Test
public void testConnectedAgain() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
activeEntity.connected(client);
Set<ClientDescriptor> connectedClients = activeEntity.getConnectedClients();
assertThat(connectedClients, hasSize(1));
assertThat(connectedClients, hasItem(client));
}
@Test
public void testConnectedSecond() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client1 = new TestClientDescriptor();
activeEntity.connected(client1);
ClientDescriptor client2 = new TestClientDescriptor();
activeEntity.connected(client2);
Set<ClientDescriptor> connectedClients = activeEntity.getConnectedClients();
assertThat(connectedClients, hasSize(2));
assertThat(connectedClients, hasItems(client1, client2));
}
@Test
public void testDisconnectedNotConnected() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client1 = new TestClientDescriptor();
activeEntity.disconnected(client1);
// Not expected to fail ...
}
/**
* Ensures the disconnect of a connected client is properly tracked.
*/
@Test
public void testDisconnected() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client1 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.disconnected(client1);
assertThat(activeEntity.getConnectedClients(), hasSize(0));
}
/**
* Ensures the disconnect of a connected client is properly tracked and does not affect others.
*/
@Test
public void testDisconnectedSecond() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client1 = new TestClientDescriptor();
activeEntity.connected(client1);
ClientDescriptor client2 = new TestClientDescriptor();
activeEntity.connected(client2);
assertThat(activeEntity.getConnectedClients(), hasSize(2));
activeEntity.disconnected(client1);
Set<ClientDescriptor> connectedClients = activeEntity.getConnectedClients();
assertThat(connectedClients, hasSize(1));
assertThat(connectedClients, hasItem(client2));
}
@Test
public void testLoadExistingRegistersEvictionListener() throws Exception {
EhcacheStateService stateService = mock(EhcacheStateService.class);
when(stateService.getClientMessageTracker(anyString())).thenReturn(mock(ClientMessageTracker.class));
ServerSideServerStore store = mock(ServerSideServerStore.class);
when(stateService.loadStore(eq(defaultStoreName), any())).thenReturn(store);
IEntityMessenger entityMessenger = mock(IEntityMessenger.class);
ServiceRegistry registry = getCustomMockedServiceRegistry(stateService, null, entityMessenger, null);
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(registry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.loadExisting();
verify(store).setEvictionListener(any(ServerStoreEvictionListener.class));
}
@Test
public void testAppendInvalidationAcksTakenIntoAccount() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client1 = new TestClientDescriptor();
ClientDescriptor client2 = new TestClientDescriptor();
ClientDescriptor client3 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.connected(client2);
activeEntity.connected(client3);
UUID client2Id = UUID.randomUUID();
UUID client3Id = UUID.randomUUID();
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// attach to the store
MESSAGE_FACTORY.setClientId(CLIENT_ID);
assertSuccess(
activeEntity.invoke(client1, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(
activeEntity.invoke(client2, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client3Id);
assertSuccess(
activeEntity.invoke(client3, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
// perform an append
assertSuccess(
activeEntity.invoke(client1, messageFactory.appendOperation(1L, createPayload(1L)))
);
// assert that an invalidation request is pending
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
InvalidationHolder invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(2));
assertThat(invalidationHolder.clientsHavingToInvalidate, containsInAnyOrder(client2, client3));
// client 2 acks
assertSuccess(
activeEntity.invoke(client2, messageFactory.clientInvalidationAck(1L, activeEntity.getClientsWaitingForInvalidation().keySet().iterator().next()))
);
// assert that client 2 is not waited for anymore
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(1));
assertThat(invalidationHolder.clientsHavingToInvalidate, contains(client3));
// client 3 acks
assertSuccess(
activeEntity.invoke(client3, messageFactory.clientInvalidationAck(1L, activeEntity.getClientsWaitingForInvalidation().keySet().iterator().next()))
);
// assert that the invalidation request is done since all clients disconnected
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(0));
}
@Test
public void testClearInvalidationAcksTakenIntoAccount() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client1 = new TestClientDescriptor();
ClientDescriptor client2 = new TestClientDescriptor();
ClientDescriptor client3 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.connected(client2);
activeEntity.connected(client3);
UUID client2Id = UUID.randomUUID();
UUID client3Id = UUID.randomUUID();
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// attach to the store
MESSAGE_FACTORY.setClientId(CLIENT_ID);
assertSuccess(
activeEntity.invoke(client1, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(
activeEntity.invoke(client2, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client3Id);
assertSuccess(
activeEntity.invoke(client3, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
// perform a clear
assertSuccess(
activeEntity.invoke(client1, messageFactory.clearOperation())
);
// assert that an invalidation request is pending
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
InvalidationHolder invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(2));
assertThat(invalidationHolder.clientsHavingToInvalidate, containsInAnyOrder(client2, client3));
// client 2 acks
assertSuccess(
activeEntity.invoke(client2, messageFactory.clientInvalidationAllAck(activeEntity.getClientsWaitingForInvalidation().keySet().iterator().next()))
);
// assert that client 2 is not waited for anymore
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(1));
assertThat(invalidationHolder.clientsHavingToInvalidate, contains(client3));
// client 3 acks
assertSuccess(
activeEntity.invoke(client3, messageFactory.clientInvalidationAllAck(activeEntity.getClientsWaitingForInvalidation().keySet().iterator().next()))
);
// assert that the invalidation request is done since all clients disconnected
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(0));
}
@Test
public void testAppendInvalidationDisconnectionOfInvalidatingClientsTakenIntoAccount() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client1 = new TestClientDescriptor();
ClientDescriptor client2 = new TestClientDescriptor();
ClientDescriptor client3 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.connected(client2);
activeEntity.connected(client3);
UUID client2Id = UUID.randomUUID();
UUID client3Id = UUID.randomUUID();
// attach to the store
MESSAGE_FACTORY.setClientId(CLIENT_ID);
assertSuccess(
activeEntity.invoke(client1, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(
activeEntity.invoke(client2, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client3Id);
assertSuccess(
activeEntity.invoke(client3, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// perform an append
assertSuccess(
activeEntity.invoke(client1, messageFactory.appendOperation(1L, createPayload(1L)))
);
// assert that an invalidation request is pending
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
InvalidationHolder invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(2));
assertThat(invalidationHolder.clientsHavingToInvalidate, containsInAnyOrder(client2, client3));
// disconnect client2
activeEntity.disconnected(client2);
// assert that client 2 is not waited for anymore
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(1));
assertThat(invalidationHolder.clientsHavingToInvalidate, contains(client3));
// disconnect client3
activeEntity.disconnected(client3);
// assert that the invalidation request is done since all clients disconnected
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(0));
}
@Test
public void testClearInvalidationDisconnectionOfInvalidatingClientsTakenIntoAccount() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client1 = new TestClientDescriptor();
ClientDescriptor client2 = new TestClientDescriptor();
ClientDescriptor client3 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.connected(client2);
activeEntity.connected(client3);
UUID client2Id = UUID.randomUUID();
UUID client3Id = UUID.randomUUID();
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// attach to the store
MESSAGE_FACTORY.setClientId(CLIENT_ID);
assertSuccess(
activeEntity.invoke(client1, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(
activeEntity.invoke(client2, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client3Id);
assertSuccess(
activeEntity.invoke(client3, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
// perform an append
assertSuccess(
activeEntity.invoke(client1, messageFactory.clearOperation())
);
// assert that an invalidation request is pending
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
InvalidationHolder invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(2));
assertThat(invalidationHolder.clientsHavingToInvalidate, containsInAnyOrder(client2, client3));
// disconnect client2
activeEntity.disconnected(client2);
// assert that client 2 is not waited for anymore
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(1));
assertThat(invalidationHolder.clientsHavingToInvalidate, contains(client3));
// disconnect client3
activeEntity.disconnected(client3);
// assert that the invalidation request is done since all clients disconnected
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(0));
}
@Test
public void testAppendInvalidationDisconnectionOfBlockingClientTakenIntoAccount() throws Exception {
ServerStoreConfiguration serverStoreConfiguration = new ServerStoreConfigBuilder()
.dedicated(defaultResource, 4, MemoryUnit.MEGABYTES)
.consistency(Consistency.STRONG)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, serverStoreConfiguration), DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client1 = new TestClientDescriptor();
ClientDescriptor client2 = new TestClientDescriptor();
ClientDescriptor client3 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.connected(client2);
activeEntity.connected(client3);
UUID client2Id = UUID.randomUUID();
UUID client3Id = UUID.randomUUID();
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// attach to the store
MESSAGE_FACTORY.setClientId(CLIENT_ID);
assertSuccess(
activeEntity.invoke(client1, MESSAGE_FACTORY.validateServerStore(defaultStoreName, serverStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(
activeEntity.invoke(client2, MESSAGE_FACTORY.validateServerStore(defaultStoreName, serverStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client3Id);
assertSuccess(
activeEntity.invoke(client3, MESSAGE_FACTORY.validateServerStore(defaultStoreName, serverStoreConfiguration))
);
// perform an append
assertSuccess(
activeEntity.invoke(client1, messageFactory.appendOperation(1L, createPayload(1L)))
);
// assert that an invalidation request is pending
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
InvalidationHolder invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(2));
assertThat(invalidationHolder.clientsHavingToInvalidate, containsInAnyOrder(client2, client3));
// disconnect client1
activeEntity.disconnected(client1);
// assert that the invalidation request is done since the originating client disconnected
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(0));
}
@Test
public void testClearInvalidationDisconnectionOfBlockingClientTakenIntoAccount() throws Exception {
ServerStoreConfiguration serverStoreConfiguration = new ServerStoreConfigBuilder()
.dedicated(defaultResource, 4, MemoryUnit.MEGABYTES)
.consistency(Consistency.STRONG)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, serverStoreConfiguration), DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client1 = new TestClientDescriptor();
ClientDescriptor client2 = new TestClientDescriptor();
ClientDescriptor client3 = new TestClientDescriptor();
activeEntity.connected(client1);
activeEntity.connected(client2);
activeEntity.connected(client3);
UUID client2Id = UUID.randomUUID();
UUID client3Id = UUID.randomUUID();
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// attach to the store
MESSAGE_FACTORY.setClientId(CLIENT_ID);
assertSuccess(
activeEntity.invoke(client1, MESSAGE_FACTORY.validateServerStore(defaultStoreName, serverStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(
activeEntity.invoke(client2, MESSAGE_FACTORY.validateServerStore(defaultStoreName, serverStoreConfiguration))
);
MESSAGE_FACTORY.setClientId(client3Id);
assertSuccess(
activeEntity.invoke(client3, MESSAGE_FACTORY.validateServerStore(defaultStoreName, serverStoreConfiguration))
);
// perform an append
assertSuccess(
activeEntity.invoke(client1, messageFactory.clearOperation())
);
// assert that an invalidation request is pending
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(1));
InvalidationHolder invalidationHolder = activeEntity.getClientsWaitingForInvalidation().values().iterator().next();
assertThat(invalidationHolder.clientDescriptorWaitingForInvalidation, is(client1));
assertThat(invalidationHolder.clientsHavingToInvalidate.size(), is(2));
assertThat(invalidationHolder.clientsHavingToInvalidate, containsInAnyOrder(client2, client3));
// disconnect client1
activeEntity.disconnected(client1);
// assert that the invalidation request is done since the originating client disconnected
assertThat(activeEntity.getClientsWaitingForInvalidation().size(), is(0));
}
@Test
public void testConnectedButNotAttachedClientFailsInvokingServerStoreOperation() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
assertFailure(
activeEntity.invoke(client, messageFactory.appendOperation(1L, createPayload(1L))),
LifecycleException.class, "Client not attached to cluster tier 'store'"
);
}
@Test
public void testWithAttachmentSucceedsInvokingServerStoreOperation() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
// attach to the store
assertSuccess(
activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
assertSuccess(
activeEntity.invoke(client, messageFactory.appendOperation(1L, createPayload(1L)))
);
EhcacheEntityResponse response = activeEntity.invoke(client, messageFactory.getOperation(1L));
assertThat(response, instanceOf(EhcacheEntityResponse.GetResponse.class));
EhcacheEntityResponse.GetResponse getResponse = (EhcacheEntityResponse.GetResponse) response;
assertThat(getResponse.getChain().isEmpty(), is(false));
}
@Test
public void testCreateDedicatedServerStore() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), containsInAnyOrder(defaultStoreName));
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(MemoryUnit.MEGABYTES.toBytes(1L)));
assertThat(activeEntity.getConnectedClients(), empty());
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(
activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration))
);
assertThat(activeEntity.getAttachedClients(), contains(client));
/*
* Ensure the dedicated resource pool remains after client disconnect.
*/
activeEntity.disconnected(client);
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), containsInAnyOrder(defaultStoreName));
assertThat(activeEntity.getConnectedClients(), empty());
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
}
@Test
public void testCreateDedicatedServerStoreExisting() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClusterTierActiveEntity otherEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
try {
otherEntity.createNew();
fail("Duplicate creation should fail with an exception");
} catch (ConfigurationException e) {
assertThat(e.getMessage(), containsString("already exists"));
}
}
@Test
public void testValidateDedicatedServerStore() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ClientDescriptor client2 = new TestClientDescriptor();
activeEntity.connected(client2);
UUID client2Id = UUID.randomUUID();
MESSAGE_FACTORY.setClientId(client2Id);
assertSuccess(activeEntity.invoke(client2,
MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), containsInAnyOrder(defaultStoreName));
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(MemoryUnit.MEGABYTES.toBytes(1L)));
assertThat(activeEntity.getConnectedClients(), hasSize(2));
assertThat(activeEntity.getAttachedClients(), contains(client2));
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
}
@Test
public void testValidateDedicatedServerStoreBad() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertFailure(activeEntity.invoke(client,
MESSAGE_FACTORY.validateServerStore(defaultStoreName,
new ServerStoreConfigBuilder()
.dedicated(defaultResource, 8, MemoryUnit.MEGABYTES)
.build())),
InvalidServerStoreConfigurationException.class);
}
@Test
public void testValidateUnknown() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName,
new ServerStoreConfigBuilder().unknown().build())));
}
@Test
public void testCreateSharedServerStore() throws Exception {
defaultRegistry.addSharedPool(defaultSharedPool, MemoryUnit.MEGABYTES.toBytes(2), defaultResource);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.shared(defaultSharedPool)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
activeEntity.createNew();
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
assertThat(defaultRegistry.getStoreManagerService().getSharedResourcePoolIds(), containsInAnyOrder(defaultSharedPool));
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), empty());
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(MemoryUnit.MEGABYTES.toBytes(2L)));
}
@Test
public void testCreateSharedServerStoreExisting() throws Exception {
defaultRegistry.addSharedPool(defaultSharedPool, MemoryUnit.MEGABYTES.toBytes(2), defaultResource);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.shared(defaultSharedPool)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
activeEntity.createNew();
ClusterTierActiveEntity otherEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
try {
otherEntity.createNew();
fail("Duplicate creation should fail with an exception");
} catch (ConfigurationException e) {
assertThat(e.getMessage(), containsString("already exists"));
}
}
@Test
public void testValidateSharedServerStore() throws Exception {
defaultRegistry.addSharedPool(defaultSharedPool, MemoryUnit.MEGABYTES.toBytes(2), defaultResource);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.shared(defaultSharedPool)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, storeConfiguration)));
assertThat(activeEntity.getAttachedClients(), contains(client));
}
@Test
public void testValidateServerStore_DedicatedStoresDifferentSizes() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.dedicated(defaultResource, 2, MemoryUnit.MEGABYTES)
.build();
String expectedMessageContent = "Existing ServerStore configuration is not compatible with the desired configuration: " +
"\n\t" +
"resourcePoolType existing: " +
defaultStoreConfiguration.getPoolAllocation() +
", desired: " +
storeConfiguration.getPoolAllocation();
assertFailure(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, storeConfiguration)),
InvalidServerStoreConfigurationException.class, expectedMessageContent);
}
@Test
public void testValidateServerStore_DedicatedStoreResourceNamesDifferent() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.dedicated("otherResource", 1, MemoryUnit.MEGABYTES)
.build();
String expectedMessageContent = "Existing ServerStore configuration is not compatible with the desired configuration: " +
"\n\t" +
"resourcePoolType existing: " +
defaultStoreConfiguration.getPoolAllocation() +
", desired: " +
storeConfiguration.getPoolAllocation();
assertFailure(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, storeConfiguration)),
InvalidServerStoreConfigurationException.class, expectedMessageContent);
}
@Test
public void testValidateServerStore_DifferentSharedPools() throws Exception {
defaultRegistry.addSharedPool(defaultSharedPool, MemoryUnit.MEGABYTES.toBytes(2), defaultResource);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.shared(defaultSharedPool)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreConfiguration otherConfiguration = new ServerStoreConfigBuilder()
.shared("other")
.build();
String expectedMessageContent = "Existing ServerStore configuration is not compatible with the desired configuration: " +
"\n\t" +
"resourcePoolType existing: " +
storeConfiguration.getPoolAllocation() +
", desired: " +
otherConfiguration.getPoolAllocation();
assertFailure(activeEntity.invoke(client,
MESSAGE_FACTORY.validateServerStore(defaultStoreName,
otherConfiguration)),InvalidServerStoreConfigurationException.class,expectedMessageContent);
}
@Test
public void testDestroyServerStore() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
activeEntity.destroy();
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(0L));
assertThat(defaultRegistry.getStoreManagerService().getStores(), empty());
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), empty());
}
/**
* Ensures shared pool and store (cache) name spaces are independent.
* The cache alias is used as the name for a {@code ServerStore} instance; this name can be
* the same as, but is independent of, the shared pool name. The
*/
@Test
public void testSharedPoolCacheNameCollision() throws Exception {
defaultRegistry.addSharedPool(defaultStoreName, MemoryUnit.MEGABYTES.toBytes(2), defaultResource);
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
assertThat(defaultRegistry.getStoreManagerService().getSharedResourcePoolIds(), contains(defaultStoreName));
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), contains(defaultStoreName));
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
}
@Test
public void testCreateNonExistentSharedPool() throws Exception {
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.shared(defaultSharedPool)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
try {
activeEntity.createNew();
fail("Creation with non-existent shared pool should have failed");
} catch (ConfigurationException e) {
assertThat(e.getMessage(), containsString("undefined"));
}
}
@Test
public void testCreateUnknownServerResource() throws Exception {
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder()
.dedicated("unknown", 2, MemoryUnit.MEGABYTES)
.build();
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
try {
activeEntity.createNew();
fail("Creation with non-existent shared pool should have failed");
} catch (ConfigurationException e) {
assertThat(e.getMessage(), containsString("Non-existent server side resource"));
}
}
@Test
public void testSyncToPassiveNoData() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
@SuppressWarnings("unchecked")
PassiveSynchronizationChannel<EhcacheEntityMessage> syncChannel = mock(PassiveSynchronizationChannel.class);
activeEntity.synchronizeKeyToPassive(syncChannel, 3);
verifyZeroInteractions(syncChannel);
}
@Test
public void testSyncToPassiveBatchedByDefault() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(UUID.randomUUID());
ByteBuffer payload = ByteBuffer.allocate(512);
// Put keys that maps to the same concurrency key
assertSuccess(activeEntity.invoke(client, messageFactory.appendOperation(1L, payload)));
assertSuccess(activeEntity.invoke(client, messageFactory.appendOperation(-2L, payload)));
assertSuccess(activeEntity.invoke(client, messageFactory.appendOperation(17L, payload)));
@SuppressWarnings("unchecked")
PassiveSynchronizationChannel<EhcacheEntityMessage> syncChannel = mock(PassiveSynchronizationChannel.class);
activeEntity.synchronizeKeyToPassive(syncChannel, 3);
verify(syncChannel).synchronizeToPassive(any(EhcacheDataSyncMessage.class));
}
@Test
public void testDataSyncToPassiveCustomBatchSize() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(UUID.randomUUID());
ByteBuffer payload = ByteBuffer.allocate(512);
// Put keys that maps to the same concurrency key
ServerStoreOpMessage.AppendMessage testMessage = messageFactory.appendOperation(1L, payload);
activeEntity.invoke(client, testMessage);
activeEntity.invoke(client, messageFactory.appendOperation(-2L, payload));
activeEntity.invoke(client, messageFactory.appendOperation(17L, payload));
activeEntity.invoke(client, messageFactory.appendOperation(33L, payload));
System.setProperty(ClusterTierActiveEntity.SYNC_DATA_SIZE_PROP, "512");
ConcurrencyStrategies.DefaultConcurrencyStrategy concurrencyStrategy = new ConcurrencyStrategies.DefaultConcurrencyStrategy(DEFAULT_MAPPER);
int concurrencyKey = concurrencyStrategy.concurrencyKey(testMessage);
try {
@SuppressWarnings("unchecked")
PassiveSynchronizationChannel<EhcacheEntityMessage> syncChannel = mock(PassiveSynchronizationChannel.class);
activeEntity.synchronizeKeyToPassive(syncChannel, concurrencyKey);
verify(syncChannel, atLeast(2)).synchronizeToPassive(any(EhcacheDataSyncMessage.class));
} finally {
System.clearProperty(ClusterTierActiveEntity.SYNC_DATA_SIZE_PROP);
}
}
@Test
public void testLoadExistingRecoversInflightInvalidationsForEventualCache() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
EhcacheStateServiceImpl ehcacheStateService = defaultRegistry.getStoreManagerService();
ehcacheStateService.createStore(defaultStoreName, defaultStoreConfiguration, false); //Passive would have done this before failover
InvalidationTracker invalidationTracker = ehcacheStateService.getInvalidationTracker(defaultStoreName);
Random random = new Random();
random.ints(0, 100).limit(10).forEach(x -> invalidationTracker.trackHashInvalidation(x));
activeEntity.loadExisting();
assertThat(activeEntity.getInflightInvalidations().isEmpty(), is(false));
}
@Test
public void testPromotedActiveIgnoresDuplicateMessages() throws Exception {
defaultRegistry.getService(new EhcacheStoreStateServiceConfig(identifier, DEFAULT_MAPPER));
EhcacheStateServiceImpl ehcacheStateService = defaultRegistry.getStoreManagerService();
ehcacheStateService.createStore(defaultStoreName, defaultStoreConfiguration, false); //Passive would have done this before failover
ClientMessageTracker clientMessageTracker = ehcacheStateService.getClientMessageTracker(defaultStoreName);
Random random = new Random();
Set<Long> msgIds = new HashSet<>();
random.longs(100).distinct().forEach(x -> {
msgIds.add(x);
});
Set<Long> applied = new HashSet<>();
msgIds.stream().limit(80).forEach(x -> {
applied.add(x);
clientMessageTracker.applied(x, CLIENT_ID);
});
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration));
activeEntity.loadExisting();
IEntityMessenger entityMessenger = defaultRegistry.getEntityMessenger();
ServerStoreMessageFactory serverStoreMessageFactory = new ServerStoreMessageFactory(CLIENT_ID);
EhcacheEntityResponseFactory entityResponseFactory = new EhcacheEntityResponseFactory();
applied.forEach(y -> {
EhcacheEntityMessage message = serverStoreMessageFactory.appendOperation(y, createPayload(y));
message.setId(y);
assertThat(activeEntity.invoke(client, message), is(entityResponseFactory.success()));
});
verify(entityMessenger, times(0)).messageSelfAndDeferRetirement(any(), any());
}
@Test
public void testReplicationMessageAndOriginalServerStoreOpMessageHasSameConcurrency() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
IEntityMessenger entityMessenger = defaultRegistry.getEntityMessenger();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
reset(entityMessenger);
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
EhcacheEntityMessage getAndAppend = messageFactory.getAndAppendOperation(1L, createPayload(1L));
activeEntity.invoke(client, getAndAppend);
ArgumentCaptor<PassiveReplicationMessage.ChainReplicationMessage> captor = ArgumentCaptor.forClass(PassiveReplicationMessage.ChainReplicationMessage.class);
verify(entityMessenger).messageSelfAndDeferRetirement(any(), captor.capture());
PassiveReplicationMessage.ChainReplicationMessage replicatedMessage = captor.getValue();
assertThat(replicatedMessage.concurrencyKey(), is(((ConcurrentEntityMessage) getAndAppend).concurrencyKey()));
}
@Test
public void testInvalidMessageThrowsError() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
try {
activeEntity.invoke(client, new InvalidMessage());
fail("Invalid message should result in AssertionError");
} catch (AssertionError e) {
assertThat(e.getMessage(), containsString("Unsupported"));
}
}
@Test
public void testActiveDoesNotTrackMessagesByDefault() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
activeEntity.createNew();
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
ServerStoreOpMessage.AppendMessage message = messageFactory.appendOperation(1L, createPayload(1L));
message.setId(123);
activeEntity.invoke(client, message);
// create another message that has the same message ID
message = messageFactory.appendOperation(2L, createPayload(1L));
message.setId(123);
activeEntity.invoke(client, message); // this invoke should be rejected due to duplicate message id
ServerStoreOpMessage.GetMessage getMessage = messageFactory.getOperation(2L);
EhcacheEntityResponse.GetResponse response = (EhcacheEntityResponse.GetResponse) activeEntity.invoke(client, getMessage);
assertThat(response.getChain().isEmpty(), is(false));
}
@Test
public void testActiveMessageTracking() throws Exception {
ClusterTierActiveEntity activeEntity = new ClusterTierActiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
EhcacheStateServiceImpl ehcacheStateService = defaultRegistry.getStoreManagerService();
ehcacheStateService.createStore(defaultStoreName, defaultStoreConfiguration, false); //hack to enable message tracking on active
ClientDescriptor client = new TestClientDescriptor();
activeEntity.connected(client);
ServerStoreMessageFactory messageFactory = new ServerStoreMessageFactory(CLIENT_ID);
assertSuccess(activeEntity.invoke(client, MESSAGE_FACTORY.validateServerStore(defaultStoreName, defaultStoreConfiguration)));
ServerStoreOpMessage.AppendMessage message = messageFactory.appendOperation(1L, createPayload(1L));
message.setId(123);
activeEntity.invoke(client, message);
// create another message that has the same message ID
message = messageFactory.appendOperation(2L, createPayload(1L));
message.setId(123);
activeEntity.invoke(client, message); // this invoke should be rejected due to duplicate message id
ServerStoreOpMessage.GetMessage getMessage = messageFactory.getOperation(2L);
EhcacheEntityResponse.GetResponse response = (EhcacheEntityResponse.GetResponse) activeEntity.invoke(client, getMessage);
assertThat(response.getChain().isEmpty(), is(true));
}
private void assertSuccess(EhcacheEntityResponse response) throws Exception {
if (!response.equals(EhcacheEntityResponse.Success.INSTANCE)) {
throw ((EhcacheEntityResponse.Failure) response).getCause();
}
}
private void assertFailure(EhcacheEntityResponse response, Class<? extends Exception> expectedException) {
assertThat(response.getResponseType(), is(EhcacheResponseType.FAILURE));
assertThat(((EhcacheEntityResponse.Failure) response).getCause(), is(instanceOf(expectedException)));
}
private void assertFailure(EhcacheEntityResponse response, Class<? extends Exception> expectedException, String expectedMessageContent) {
assertThat(response.getResponseType(), is(EhcacheResponseType.FAILURE));
Exception cause = ((EhcacheEntityResponse.Failure) response).getCause();
assertThat(cause, is(instanceOf(expectedException)));
assertThat(cause.getMessage(), containsString(expectedMessageContent));
}
@SuppressWarnings("unchecked")
ServiceRegistry getCustomMockedServiceRegistry(EhcacheStateService stateService, ClientCommunicator clientCommunicator,
IEntityMessenger entityMessenger, EntityMonitoringService entityMonitoringService) {
return new ServiceRegistry() {
@Override
public <T> T getService(final ServiceConfiguration<T> configuration) {
Class<T> serviceType = configuration.getServiceType();
if (serviceType.isAssignableFrom(ClientCommunicator.class)) {
return (T) clientCommunicator;
} else if (serviceType.isAssignableFrom(IEntityMessenger.class)) {
return (T) entityMessenger;
} else if (serviceType.isAssignableFrom(EhcacheStateService.class)) {
return (T) stateService;
} else if (serviceType.isAssignableFrom(EntityMonitoringService.class)) {
return (T) entityMonitoringService;
}
throw new AssertionError("Unknown service configuration of type: " + serviceType);
}
};
}
/**
* Builder for {@link ServerStoreConfiguration} instances.
*/
private static final class ServerStoreConfigBuilder {
private PoolAllocation poolAllocation;
private String storedKeyType = "java.lang.Long";
private String storedValueType = "java.lang.String";
private String keySerializerType;
private String valueSerializerType;
private Consistency consistency = Consistency.EVENTUAL;
ServerStoreConfigBuilder consistency(Consistency consistency) {
this.consistency = consistency;
return this;
}
ServerStoreConfigBuilder dedicated(String resourceName, int size, MemoryUnit unit) {
this.poolAllocation = new Dedicated(resourceName, unit.toBytes(size));
return this;
}
ServerStoreConfigBuilder shared(String resourcePoolName) {
this.poolAllocation = new Shared(resourcePoolName);
return this;
}
ServerStoreConfigBuilder unknown() {
this.poolAllocation = new PoolAllocation.Unknown();
return this;
}
ServerStoreConfigBuilder setStoredKeyType(Class<?> storedKeyType) {
this.storedKeyType = storedKeyType.getName();
return this;
}
ServerStoreConfigBuilder setStoredValueType(Class<?> storedValueType) {
this.storedValueType = storedValueType.getName();
return this;
}
ServerStoreConfigBuilder setKeySerializerType(Class<?> keySerializerType) {
this.keySerializerType = keySerializerType.getName();
return this;
}
ServerStoreConfigBuilder setValueSerializerType(Class<?> valueSerializerType) {
this.valueSerializerType = valueSerializerType.getName();
return this;
}
ServerStoreConfiguration build() {
return new ServerStoreConfiguration(poolAllocation, storedKeyType, storedValueType,
keySerializerType, valueSerializerType, consistency);
}
}
private static final class TestClientDescriptor implements ClientDescriptor {
private static final AtomicInteger counter = new AtomicInteger(0);
private final int clientId = counter.incrementAndGet();
@Override
public String toString() {
return "TestClientDescriptor[" + clientId + "]";
}
}
/**
* Provides a {@link ServiceRegistry} for off-heap resources. This is a "server-side" object.
*/
private static final class OffHeapIdentifierRegistry implements ServiceRegistry {
private final long offHeapSize;
private final String defaultResource;
private EhcacheStateServiceImpl storeManagerService;
private IEntityMessenger entityMessenger;
private ClientCommunicator clientCommunicator;
private final Map<OffHeapResourceIdentifier, TestOffHeapResource> pools =
new HashMap<OffHeapResourceIdentifier, TestOffHeapResource>();
private final Map<String, ServerSideConfiguration.Pool> sharedPools = new HashMap<>();
/**
* Instantiate an "open" {@code ServiceRegistry}. Using this constructor creates a
* registry that creates {@code OffHeapResourceIdentifier} entries as they are
* referenced.
*/
private OffHeapIdentifierRegistry(String defaultResource) {
this.defaultResource = defaultResource;
this.offHeapSize = 0;
}
/**
* Instantiate a "closed" {@code ServiceRegistry}. Using this constructor creates a
* registry that only returns {@code OffHeapResourceIdentifier} entries supplied
* through the {@link #addResource} method.
*/
private OffHeapIdentifierRegistry() {
this(null);
}
private void addSharedPool(String name, long size, String resourceName) {
sharedPools.put(name, new ServerSideConfiguration.Pool(size, resourceName));
}
/**
* Adds an off-heap resource of the given name to this registry.
*
* @param name the name of the resource
* @param offHeapSize the off-heap size
* @param unit the size unit type
* @return {@code this} {@code OffHeapIdentifierRegistry}
*/
private OffHeapIdentifierRegistry addResource(String name, int offHeapSize, MemoryUnit unit) {
this.pools.put(OffHeapResourceIdentifier.identifier(name), new TestOffHeapResource(unit.toBytes(offHeapSize)));
return this;
}
private TestOffHeapResource getResource(String resourceName) {
return this.pools.get(OffHeapResourceIdentifier.identifier(resourceName));
}
private EhcacheStateServiceImpl getStoreManagerService() {
return this.storeManagerService;
}
private IEntityMessenger getEntityMessenger() {
return entityMessenger;
}
private ClientCommunicator getClientCommunicator() {
return clientCommunicator;
}
private static Set<String> getIdentifiers(Set<OffHeapResourceIdentifier> pools) {
Set<String> names = new HashSet<String>();
for (OffHeapResourceIdentifier identifier: pools) {
names.add(identifier.getName());
}
return Collections.unmodifiableSet(names);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getService(ServiceConfiguration<T> serviceConfiguration) {
if (serviceConfiguration.getServiceType().equals(ClientCommunicator.class)) {
if (this.clientCommunicator == null) {
this.clientCommunicator = mock(ClientCommunicator.class);
}
return (T) this.clientCommunicator;
} else if (serviceConfiguration.getServiceType().equals(EhcacheStateService.class)) {
if (storeManagerService == null) {
this.storeManagerService = new EhcacheStateServiceImpl(new OffHeapResources() {
@Override
public Set<OffHeapResourceIdentifier> getAllIdentifiers() {
return pools.keySet();
}
@Override
public OffHeapResource getOffHeapResource(OffHeapResourceIdentifier identifier) {
return pools.get(identifier);
}
}, new ServerSideConfiguration(sharedPools), DEFAULT_MAPPER, service -> {});
try {
this.storeManagerService.configure();
} catch (ConfigurationException e) {
throw new AssertionError("Test setup failed!");
}
}
return (T) (this.storeManagerService);
} else if (serviceConfiguration.getServiceType().equals(IEntityMessenger.class)) {
if (this.entityMessenger == null) {
this.entityMessenger = mock(IEntityMessenger.class);
}
return (T) this.entityMessenger;
} else if(serviceConfiguration instanceof ConsumerManagementRegistryConfiguration) {
return null;
} else if(serviceConfiguration instanceof ActiveEntityMonitoringServiceConfiguration) {
return null;
}
throw new UnsupportedOperationException("Registry.getService does not support " + serviceConfiguration.getClass().getName());
}
}
/**
* Testing implementation of {@link OffHeapResource}. This is a "server-side" object.
*/
private static final class TestOffHeapResource implements OffHeapResource {
private long capacity;
private long used;
private TestOffHeapResource(long capacity) {
this.capacity = capacity;
}
@Override
public boolean reserve(long size) throws IllegalArgumentException {
if (size < 0) {
throw new IllegalArgumentException();
}
if (size > available()) {
return false;
} else {
this.used += size;
return true;
}
}
@Override
public void release(long size) throws IllegalArgumentException {
if (size < 0) {
throw new IllegalArgumentException();
}
this.used -= size;
}
@Override
public long available() {
return this.capacity - this.used;
}
@Override
public long capacity() {
return capacity;
}
private long getUsed() {
return used;
}
}
private static class InvalidMessage extends EhcacheEntityMessage {
@Override
public void setId(long id) {
throw new UnsupportedOperationException("TODO Implement me!");
}
@Override
public long getId() {
throw new UnsupportedOperationException("TODO Implement me!");
}
@Override
public UUID getClientId() {
throw new UnsupportedOperationException("TODO Implement me!");
}
}
}