/*
* 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.ServerSideConfiguration;
import org.ehcache.clustered.common.internal.ServerStoreConfiguration;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityMessage;
import org.ehcache.clustered.common.internal.messages.LifeCycleMessageFactory;
import org.ehcache.clustered.common.internal.store.ClusterTierEntityConfiguration;
import org.ehcache.clustered.server.EhcacheStateServiceImpl;
import org.ehcache.clustered.server.KeySegmentMapper;
import org.ehcache.clustered.server.state.EhcacheStateService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.terracotta.entity.BasicServiceConfiguration;
import org.terracotta.entity.ConfigurationException;
import org.terracotta.entity.IEntityMessenger;
import org.terracotta.entity.ServiceConfiguration;
import org.terracotta.entity.ServiceRegistry;
import org.terracotta.management.service.monitoring.ConsumerManagementRegistryConfiguration;
import org.terracotta.management.service.monitoring.PassiveEntityMonitoringServiceConfiguration;
import org.terracotta.monitoring.IMonitoringProducer;
import org.terracotta.offheapresource.OffHeapResource;
import org.terracotta.offheapresource.OffHeapResourceIdentifier;
import org.terracotta.offheapresource.OffHeapResources;
import org.terracotta.offheapstore.util.MemoryUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
public class ClusterTierPassiveEntityTest {
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 ClusterTierPassiveEntity(mock(ServiceRegistry.class), null, DEFAULT_MAPPER);
}
@Test
public void testCreateDedicatedServerStore() throws Exception {
ClusterTierPassiveEntity passiveEntity = new ClusterTierPassiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
passiveEntity.createNew();
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), containsInAnyOrder(defaultStoreName));
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(MemoryUnit.MEGABYTES.toBytes(1L)));
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
}
@Test
public void testCreateSharedServerStore() throws Exception {
defaultRegistry.addSharedPool(defaultSharedPool, MemoryUnit.MEGABYTES.toBytes(2), defaultResource);
ServerStoreConfiguration storeConfiguration = new ServerStoreConfigBuilder().shared(defaultSharedPool).build();
ClusterTierPassiveEntity passiveEntity = new ClusterTierPassiveEntity(defaultRegistry,
new ClusterTierEntityConfiguration(identifier, defaultStoreName, storeConfiguration), DEFAULT_MAPPER);
passiveEntity.createNew();
assertThat(defaultRegistry.getStoreManagerService().getStores(), containsInAnyOrder(defaultStoreName));
assertThat(defaultRegistry.getStoreManagerService()
.getSharedResourcePoolIds(), containsInAnyOrder(defaultSharedPool));
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), is(Matchers.<String>empty()));
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(MemoryUnit.MEGABYTES.toBytes(2L)));
}
@Test
public void testDestroyServerStore() throws Exception {
ClusterTierPassiveEntity passiveEntity = new ClusterTierPassiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
passiveEntity.createNew();
passiveEntity.destroy();
assertThat(defaultRegistry.getStoreManagerService().getStores(), is(Matchers.<String>empty()));
assertThat(defaultRegistry.getStoreManagerService().getDedicatedResourcePoolIds(), is(Matchers.<String>empty()));
assertThat(defaultRegistry.getResource(defaultResource).getUsed(), is(0L));
}
@Test
public void testInvalidMessageThrowsError() throws Exception {
ClusterTierPassiveEntity passiveEntity = new ClusterTierPassiveEntity(defaultRegistry, defaultConfiguration, DEFAULT_MAPPER);
try {
passiveEntity.invoke(new InvalidMessage());
fail("Invalid message should result in AssertionError");
} catch (AssertionError e) {
assertThat(e.getMessage(), containsString("Unsupported"));
}
}
private static ServerSideConfiguration.Pool pool(String resourceName, int poolSize, MemoryUnit unit) {
return new ServerSideConfiguration.Pool(unit.toBytes(poolSize), resourceName);
}
/**
* Builder for {@link ServerStoreConfiguration} instances.
*/
private static final class ServerStoreConfigBuilder {
private PoolAllocation poolAllocation;
private String storedKeyType;
private String storedValueType;
private String keySerializerType;
private String valueSerializerType;
private Consistency consistency;
ServerStoreConfigBuilder consistency(Consistency consistency) {
this.consistency = consistency;
return this;
}
ServerStoreConfigBuilder dedicated(String resourceName, int size, MemoryUnit unit) {
this.poolAllocation = new PoolAllocation.Dedicated(resourceName, unit.toBytes(size));
return this;
}
ServerStoreConfigBuilder shared(String resourcePoolName) {
this.poolAllocation = new PoolAllocation.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);
}
}
/**
* 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 EhcacheStateServiceImpl storeManagerService;
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(int offHeapSize, MemoryUnit unit) {
this.offHeapSize = unit.toBytes(offHeapSize);
}
/**
* 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.offHeapSize = 0;
}
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 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(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)) {
return (T) mock(IEntityMessenger.class);
} else if(serviceConfiguration instanceof ConsumerManagementRegistryConfiguration) {
return null;
} else if(serviceConfiguration instanceof PassiveEntityMonitoringServiceConfiguration) {
return null;
} else if(serviceConfiguration instanceof BasicServiceConfiguration && serviceConfiguration.getServiceType() == IMonitoringProducer.class) {
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!");
}
}
}