/* * 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; import org.ehcache.clustered.common.ServerSideConfiguration; import org.ehcache.clustered.common.internal.ClusterTierManagerConfiguration; import org.ehcache.clustered.common.internal.exceptions.DestroyInProgressException; import org.ehcache.clustered.common.internal.messages.EhcacheEntityMessage; import org.ehcache.clustered.common.internal.messages.LifeCycleMessageFactory; import org.ehcache.clustered.common.internal.messages.LifecycleMessage; import org.ehcache.clustered.server.management.Management; import org.ehcache.clustered.server.state.EhcacheStateService; import org.ehcache.clustered.server.state.config.EhcacheStateServiceConfig; 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.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; public class ClusterTierManagerPassiveEntityTest { 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); @Before public void setClientId() { MESSAGE_FACTORY.setClientId(CLIENT_ID); } @Test(expected = ConfigurationException.class) public void testConfigNull() throws Exception { new ClusterTierManagerPassiveEntity(null, null, null); } /** * Ensures basic shared resource pool configuration. */ @Test public void testConfigure() throws Exception { OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(32, MemoryUnit.MEGABYTES); registry.addResource("defaultServerResource", 8, MemoryUnit.MEGABYTES); registry.addResource("serverResource1", 8, MemoryUnit.MEGABYTES); registry.addResource("serverResource2", 8, MemoryUnit.MEGABYTES); ServerSideConfiguration serverSideConfiguration = new ServerSideConfigBuilder() .defaultResource("defaultServerResource") .sharedPool("primary", "serverResource1", 4, MemoryUnit.MEGABYTES) .sharedPool("secondary", "serverResource2", 8, MemoryUnit.MEGABYTES) .build(); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", serverSideConfiguration); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); final ClusterTierManagerPassiveEntity passiveEntity = new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); assertThat(registry.getStoreManagerService() .getSharedResourcePoolIds(), containsInAnyOrder("primary", "secondary")); assertThat(registry.getResource("serverResource1").getUsed(), is(MemoryUnit.MEGABYTES.toBytes(4L))); assertThat(registry.getResource("serverResource2").getUsed(), is(MemoryUnit.MEGABYTES.toBytes(8L))); assertThat(registry.getResource("defaultServerResource").getUsed(), is(0L)); assertThat(registry.getStoreManagerService().getStores(), is(Matchers.<String>empty())); } /** * Ensure configuration fails when specifying non-existent resource. */ @Test public void testConfigureMissingPoolResource() throws Exception { final OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(); registry.addResource("serverResource1", 32, MemoryUnit.MEGABYTES); registry.addResource("defaultServerResource", 64, MemoryUnit.MEGABYTES); ServerSideConfiguration serverSideConfiguration = new ServerSideConfigBuilder() .defaultResource("defaultServerResource") .sharedPool("primary", "serverResource1", 4, MemoryUnit.MEGABYTES) .sharedPool("secondary", "serverResource2", 8, MemoryUnit.MEGABYTES) // missing on 'server' .build(); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", serverSideConfiguration); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); try { new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); fail("Entity creation should have failed"); } catch (ConfigurationException e) { assertThat(e.getMessage(), containsString("server side resource")); } assertThat(registry.getStoreManagerService().getSharedResourcePoolIds(), is(Matchers.<String>empty())); assertThat(registry.getResource("serverResource1").getUsed(), is(0L)); assertThat(registry.getResource("serverResource2"), is(nullValue())); assertThat(registry.getResource("defaultServerResource").getUsed(), is(0L)); assertThat(registry.getStoreManagerService().getStores(), is(Matchers.<String>empty())); } /** * Ensure configuration fails when specifying non-existent default resource. */ @Test public void testConfigureMissingDefaultResource() throws Exception { final OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(); registry.addResource("serverResource1", 32, MemoryUnit.MEGABYTES); registry.addResource("serverResource2", 32, MemoryUnit.MEGABYTES); ServerSideConfiguration serverSideConfiguration = new ServerSideConfigBuilder() .defaultResource("defaultServerResource") .sharedPool("primary", "serverResource1", 4, MemoryUnit.MEGABYTES) .sharedPool("secondary", "serverResource2", 8, MemoryUnit.MEGABYTES) .build(); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", serverSideConfiguration); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); try { new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); fail("Entity creation should have failed"); } catch (ConfigurationException e) { assertThat(e.getMessage(), containsString("not defined")); } assertThat(registry.getStoreManagerService().getSharedResourcePoolIds(), is(Matchers.<String>empty())); assertThat(registry.getResource("serverResource1").getUsed(), is(0L)); assertThat(registry.getResource("serverResource2").getUsed(), is(0L)); assertThat(registry.getResource("defaultServerResource"), is(nullValue())); assertThat(registry.getStoreManagerService().getStores(), is(Matchers.<String>empty())); } @Test public void testConfigureLargeSharedPool() throws Exception { final OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(); registry.addResource("defaultServerResource", 64, MemoryUnit.MEGABYTES); registry.addResource("serverResource1", 32, MemoryUnit.MEGABYTES); registry.addResource("serverResource2", 32, MemoryUnit.MEGABYTES); ServerSideConfiguration serverSideConfiguration = new ServerSideConfigBuilder() .defaultResource("defaultServerResource") .sharedPool("primary", "serverResource1", 4, MemoryUnit.MEGABYTES) .sharedPool("secondary", "serverResource2", 8, MemoryUnit.MEGABYTES) .sharedPool("tooBig", "serverResource2", 64, MemoryUnit.MEGABYTES) .build(); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", serverSideConfiguration); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); try { new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); fail("Entity creation should have failed"); } catch (ConfigurationException e) { assertThat(e.getMessage(), containsString("resources to allocate")); } final Set<String> poolIds = registry.getStoreManagerService().getSharedResourcePoolIds(); assertThat(poolIds, is(Matchers.<String>empty())); assertThat(registry.getResource("serverResource1").getUsed(), is(0L)); assertThat(registry.getResource("serverResource2").getUsed(), is(0L)); assertThat(registry.getResource("defaultServerResource").getUsed(), is(0L)); assertThat(registry.getStoreManagerService().getStores(), is(Matchers.<String>empty())); } @Test public void testDestroyWithStores() throws Exception { OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(32, MemoryUnit.MEGABYTES); registry.addResource("serverResource1", 32, MemoryUnit.MEGABYTES); registry.addResource("serverResource2", 32, MemoryUnit.MEGABYTES); ServerSideConfiguration serverSideConfiguration = new ServerSideConfigBuilder() .sharedPool("primary", "serverResource1", 4, MemoryUnit.MEGABYTES) .sharedPool("secondary", "serverResource2", 8, MemoryUnit.MEGABYTES) .build(); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", serverSideConfiguration); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); final ClusterTierManagerPassiveEntity passiveEntity = new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); assertThat(registry.getResource("serverResource1").getUsed(), is(MemoryUnit.MEGABYTES.toBytes(4L))); assertThat(registry.getResource("serverResource2").getUsed(), is(MemoryUnit.MEGABYTES.toBytes(8L))); assertThat(registry.getStoreManagerService().getSharedResourcePoolIds(), containsInAnyOrder("primary", "secondary")); passiveEntity.destroy(); assertThat(registry.getStoreManagerService().getSharedResourcePoolIds(), is(Matchers.<String>empty())); assertThat(registry.getResource("serverResource1").getUsed(), is(MemoryUnit.MEGABYTES.toBytes(0L))); assertThat(registry.getResource("serverResource2").getUsed(), is(MemoryUnit.MEGABYTES.toBytes(0L))); } @Test public void testInvalidMessageThrowsError() throws Exception { OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(4, MemoryUnit.MEGABYTES); registry.addResource("serverResource", 4, MemoryUnit.MEGABYTES); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", new ServerSideConfigBuilder() .build()); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); final ClusterTierManagerPassiveEntity passiveEntity = new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); try { passiveEntity.invoke(new InvalidMessage()); fail("Invalid message should result in AssertionError"); } catch (AssertionError e) { assertThat(e.getMessage(), containsString("Unsupported EhcacheEntityMessage")); } } @Test public void testPrepareForDestroy() throws Exception { OffHeapIdentifierRegistry registry = new OffHeapIdentifierRegistry(4, MemoryUnit.MEGABYTES); registry.addResource("serverResource", 4, MemoryUnit.MEGABYTES); ClusterTierManagerConfiguration configuration = new ClusterTierManagerConfiguration("identifier", new ServerSideConfigBuilder() .build()); EhcacheStateService ehcacheStateService = registry.getService(new EhcacheStateServiceConfig(configuration, registry, DEFAULT_MAPPER)); Management management = new Management(registry, ehcacheStateService, false, configuration.getIdentifier()); final ClusterTierManagerPassiveEntity passiveEntity = new ClusterTierManagerPassiveEntity(configuration, ehcacheStateService, management); passiveEntity.invoke(new LifecycleMessage.PrepareForDestroy()); try { ehcacheStateService.validate(null); } catch (DestroyInProgressException e) { assertThat(e.getMessage(), containsString("in progress for destroy")); } } private static ServerSideConfiguration.Pool pool(String resourceName, int poolSize, MemoryUnit unit) { return new ServerSideConfiguration.Pool(unit.toBytes(poolSize), resourceName); } private static final class ServerSideConfigBuilder { private final Map<String, ServerSideConfiguration.Pool> pools = new HashMap<String, ServerSideConfiguration.Pool>(); private String defaultServerResource; ServerSideConfigBuilder sharedPool(String poolName, String resourceName, int size, MemoryUnit unit) { pools.put(poolName, pool(resourceName, size, unit)); return this; } ServerSideConfigBuilder defaultResource(String resourceName) { this.defaultServerResource = resourceName; return this; } ServerSideConfiguration build() { if (defaultServerResource == null) { return new ServerSideConfiguration(pools); } else { return new ServerSideConfiguration(defaultServerResource, pools); } } } /** * 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>(); /** * 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; } /** * 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)) { EhcacheStateServiceConfig config = (EhcacheStateServiceConfig) serviceConfiguration; 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); } }, config.getConfig().getConfiguration(), DEFAULT_MAPPER, service -> {}); } 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!"); } } }