/*
* 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.client.internal.service;
import org.ehcache.CachePersistenceException;
import org.ehcache.clustered.client.config.ClusteringServiceConfiguration;
import org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder;
import org.ehcache.clustered.client.config.builders.ClusteringServiceConfigurationBuilder;
import org.ehcache.clustered.client.internal.ClusterTierManagerValidationException;
import org.ehcache.clustered.client.internal.SimpleClusterTierManagerClientEntity;
import org.ehcache.clustered.client.internal.UnitTestConnectionService;
import org.ehcache.clustered.client.service.ClusteringService;
import org.ehcache.clustered.common.internal.exceptions.ClusterException;
import org.ehcache.clustered.common.internal.exceptions.InvalidServerSideConfigurationException;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.config.store.StoreEventSourceConfiguration;
import org.ehcache.core.internal.store.StoreConfigurationImpl;
import org.ehcache.core.spi.store.Store;
import org.ehcache.impl.internal.spi.serialization.DefaultSerializationProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.URI;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
/**
* This class includes tests to ensure server-side exceptions returned as responses to
* {@link SimpleClusterTierManagerClientEntity} messages are wrapped before being re-thrown. This class
* relies on {@link DefaultClusteringService} to set up conditions for the test and
* is placed accordingly.
*/
public class ClusterTierManagerClientEntityExceptionTest {
private static final String CLUSTER_URI_BASE = "terracotta://example.com:9540/";
@Before
public void definePassThroughServer() throws Exception {
UnitTestConnectionService.add(CLUSTER_URI_BASE,
new UnitTestConnectionService.PassthroughServerBuilder()
.resource("defaultResource", 128, MemoryUnit.MB)
.resource("serverResource1", 32, MemoryUnit.MB)
.resource("serverResource2", 32, MemoryUnit.MB)
.build());
}
@After
public void removePassThroughServer() throws Exception {
UnitTestConnectionService.remove(CLUSTER_URI_BASE);
}
/**
* Tests to ensure that a {@link ClusterException ClusterException}
* originating in the server is properly wrapped on the client before being re-thrown.
*/
@Test
public void testServerExceptionPassThrough() throws Exception {
ClusteringServiceConfiguration creationConfig =
ClusteringServiceConfigurationBuilder.cluster(URI.create(CLUSTER_URI_BASE + "my-application"))
.autoCreate()
.defaultServerResource("defaultResource")
.resourcePool("sharedPrimary", 2, MemoryUnit.MB, "serverResource1")
.resourcePool("sharedSecondary", 2, MemoryUnit.MB, "serverResource2")
.resourcePool("sharedTertiary", 4, MemoryUnit.MB)
.build();
DefaultClusteringService creationService = new DefaultClusteringService(creationConfig);
creationService.start(null);
creationService.stop();
ClusteringServiceConfiguration accessConfig =
ClusteringServiceConfigurationBuilder.cluster(URI.create(CLUSTER_URI_BASE + "my-application"))
.expecting()
.defaultServerResource("different")
.build();
DefaultClusteringService accessService = new DefaultClusteringService(accessConfig);
/*
* Induce an "InvalidStoreException: cluster tier 'cacheAlias' does not exist" on the server.
*/
try {
accessService.start(null);
fail("Expecting ClusterTierManagerValidationException");
} catch (ClusterTierManagerValidationException e) {
/*
* Find the last ClusterTierManagerClientEntity involved exception in the causal chain. This
* is where the server-side exception should have entered the client.
*/
Throwable clientSideException = null;
for (Throwable t = e; t.getCause() != null && t.getCause() != t; t = t.getCause()) {
for (StackTraceElement element : t.getStackTrace()) {
if (element.getClassName().endsWith("ClusterTierManagerClientEntity")) {
clientSideException = t;
}
}
}
assert clientSideException != null;
/*
* In this specific failure case, the exception is expected to be an InvalidStoreException from
* the server and re-thrown in the client.
*/
Throwable clientSideCause = clientSideException.getCause();
assertThat(clientSideCause, is(instanceOf(InvalidServerSideConfigurationException.class)));
serverCheckLoop:
{
for (StackTraceElement element : clientSideCause.getStackTrace()) {
if (element.getClassName().endsWith("ClusterTierManagerActiveEntity")) {
break serverCheckLoop;
}
}
fail(clientSideException + " lacks server-based cause");
}
assertThat(clientSideException, is(instanceOf(InvalidServerSideConfigurationException.class)));
} finally {
accessService.stop();
}
}
private <K, V> Store.Configuration<K, V> getDedicatedStoreConfig(
String targetResource, DefaultSerializationProvider serializationProvider, Class<K> keyType, Class<V> valueType)
throws org.ehcache.spi.serialization.UnsupportedTypeException {
return new StoreConfigurationImpl<K, V>(
CacheConfigurationBuilder.newCacheConfigurationBuilder(keyType, valueType,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.with(ClusteredResourcePoolBuilder.clusteredDedicated(targetResource, 8, MemoryUnit.MB)))
.build(),
StoreEventSourceConfiguration.DEFAULT_DISPATCHER_CONCURRENCY,
serializationProvider.createKeySerializer(keyType, getClass().getClassLoader()),
serializationProvider.createValueSerializer(valueType, getClass().getClassLoader()));
}
private ClusteringService.ClusteredCacheIdentifier getClusteredCacheIdentifier(
DefaultClusteringService service, String cacheAlias)
throws CachePersistenceException {
ClusteringService.ClusteredCacheIdentifier clusteredCacheIdentifier = (ClusteringService.ClusteredCacheIdentifier) service.getPersistenceSpaceIdentifier(cacheAlias, null);
if (clusteredCacheIdentifier != null) {
return clusteredCacheIdentifier;
}
throw new AssertionError("ClusteredCacheIdentifier not available for configuration");
}
}