/* * 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.management; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.ehcache.CacheManager; import org.ehcache.Status; import org.ehcache.config.units.EntryUnit; import org.ehcache.config.units.MemoryUnit; import org.ehcache.management.registry.DefaultManagementRegistryConfiguration; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.rules.Timeout; import org.terracotta.connection.Connection; import org.terracotta.management.entity.nms.NmsConfig; import org.terracotta.management.entity.nms.client.DefaultNmsService; import org.terracotta.management.entity.nms.client.NmsEntity; import org.terracotta.management.entity.nms.client.NmsEntityFactory; import org.terracotta.management.entity.nms.client.NmsService; import org.terracotta.management.model.cluster.Client; import org.terracotta.management.model.cluster.ClientIdentifier; import org.terracotta.management.model.cluster.ServerEntityIdentifier; import org.terracotta.management.model.context.Context; import org.terracotta.management.model.message.Message; import org.terracotta.management.model.notification.ContextualNotification; import org.terracotta.management.model.stats.ContextualStatistics; import org.terracotta.testing.rules.BasicExternalCluster; import org.terracotta.testing.rules.Cluster; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Scanner; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder.clusteredDedicated; import static org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder.clusteredShared; import static org.ehcache.clustered.client.config.builders.ClusteringServiceConfigurationBuilder.cluster; import static org.ehcache.config.builders.CacheConfigurationBuilder.newCacheConfigurationBuilder; import static org.ehcache.config.builders.CacheManagerBuilder.newCacheManagerBuilder; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; public abstract class AbstractClusteringManagementTest { private static final String RESOURCE_CONFIG = "<config xmlns:ohr='http://www.terracotta.org/config/offheap-resource'>" + "<ohr:offheap-resources>" + "<ohr:resource name=\"primary-server-resource\" unit=\"MB\">64</ohr:resource>" + "<ohr:resource name=\"secondary-server-resource\" unit=\"MB\">64</ohr:resource>" + "</ohr:offheap-resources>" + "</config>\n"; protected static CacheManager cacheManager; protected static ClientIdentifier ehcacheClientIdentifier; protected static ServerEntityIdentifier clusterTierManagerEntityIdentifier; protected static ObjectMapper mapper = new ObjectMapper(); protected static NmsService nmsService; protected static ServerEntityIdentifier tmsServerEntityIdentifier; protected static Connection managementConnection; @ClassRule public static Cluster CLUSTER = new BasicExternalCluster(new File("build/cluster"), 1, Collections.emptyList(), "", RESOURCE_CONFIG, ""); @BeforeClass public static void beforeClass() throws Exception { mapper.configure(SerializationFeature.INDENT_OUTPUT, true); CLUSTER.getClusterControl().waitForActive(); // simulate a TMS client managementConnection = CLUSTER.newConnection(); NmsEntityFactory entityFactory = new NmsEntityFactory(managementConnection, AbstractClusteringManagementTest.class.getName()); NmsEntity tmsAgentEntity = entityFactory.retrieveOrCreate(new NmsConfig()); nmsService = new DefaultNmsService(tmsAgentEntity); nmsService.setOperationTimeout(5, TimeUnit.SECONDS); tmsServerEntityIdentifier = readTopology() .activeServerEntityStream() .filter(serverEntity -> serverEntity.getType().equals(NmsConfig.ENTITY_TYPE)) .findFirst() .get() // throws if not found .getServerEntityIdentifier(); cacheManager = newCacheManagerBuilder() // cluster config .with(cluster(CLUSTER.getConnectionURI().resolve("/my-server-entity-1")) .autoCreate() .defaultServerResource("primary-server-resource") .resourcePool("resource-pool-a", 28, MemoryUnit.MB, "secondary-server-resource") // <2> .resourcePool("resource-pool-b", 16, MemoryUnit.MB)) // will take from primary-server-resource // management config .using(new DefaultManagementRegistryConfiguration() .addTags("webapp-1", "server-node-1") .setCacheManagerAlias("my-super-cache-manager")) // cache config .withCache("dedicated-cache-1", newCacheConfigurationBuilder( String.class, String.class, newResourcePoolsBuilder() .heap(10, EntryUnit.ENTRIES) .offheap(1, MemoryUnit.MB) .with(clusteredDedicated("primary-server-resource", 4, MemoryUnit.MB))) .build()) .withCache("shared-cache-2", newCacheConfigurationBuilder( String.class, String.class, newResourcePoolsBuilder() .heap(10, EntryUnit.ENTRIES) .offheap(1, MemoryUnit.MB) .with(clusteredShared("resource-pool-a"))) .build()) .withCache("shared-cache-3", newCacheConfigurationBuilder( String.class, String.class, newResourcePoolsBuilder() .heap(10, EntryUnit.ENTRIES) .offheap(1, MemoryUnit.MB) .with(clusteredShared("resource-pool-b"))) .build()) .build(true); // ensure the CM is running and get its client id assertThat(cacheManager.getStatus(), equalTo(Status.AVAILABLE)); ehcacheClientIdentifier = readTopology().getClients().values() .stream() .filter(client -> client.getName().equals("Ehcache:my-server-entity-1")) .findFirst() .map(Client::getClientIdentifier) .get(); clusterTierManagerEntityIdentifier = readTopology() .activeServerEntityStream() .filter(serverEntity -> serverEntity.getName().equals("my-server-entity-1")) .findFirst() .get() // throws if not found .getServerEntityIdentifier(); // test_notifs_sent_at_CM_init waitForAllNotifications( "CLIENT_CONNECTED", "CLIENT_REGISTRY_AVAILABLE", "CLIENT_TAGS_UPDATED", "EHCACHE_CLIENT_VALIDATED", "EHCACHE_RESOURCE_POOLS_CONFIGURED", "EHCACHE_SERVER_STORE_CREATED", "EHCACHE_SERVER_STORE_CREATED", "EHCACHE_SERVER_STORE_CREATED", "ENTITY_REGISTRY_AVAILABLE", "ENTITY_REGISTRY_AVAILABLE", "ENTITY_REGISTRY_AVAILABLE", "ENTITY_REGISTRY_AVAILABLE", "ENTITY_REGISTRY_AVAILABLE", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_CREATED", "SERVER_ENTITY_DESTROYED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_FETCHED", "SERVER_ENTITY_UNFETCHED", "SERVER_ENTITY_UNFETCHED", "SERVER_ENTITY_UNFETCHED" ); sendManagementCallOnEntityToCollectStats(); } @AfterClass public static void afterClass() throws Exception { if (cacheManager != null && cacheManager.getStatus() == Status.AVAILABLE) { if (nmsService != null) { Context ehcacheClient = readTopology().getClient(ehcacheClientIdentifier).get().getContext().with("cacheManagerName", "my-super-cache-manager"); nmsService.stopStatisticCollector(ehcacheClient).waitForReturn(); } cacheManager.close(); } if (nmsService != null) { Context context = readTopology().getSingleStripe().getActiveServerEntity(tmsServerEntityIdentifier).get().getContext(); nmsService.stopStatisticCollector(context); managementConnection.close(); } } @Rule public final Timeout globalTimeout = Timeout.seconds(60); @Before public void init() throws Exception { if (nmsService != null) { // this call clear the CURRRENT arrived messages, but be aware that some other messages can arrive just after the drain nmsService.readMessages(); } } protected static org.terracotta.management.model.cluster.Cluster readTopology() throws Exception { return nmsService.readTopology(); } protected static void sendManagementCallOnClientToCollectStats() throws Exception { Context ehcacheClient = readTopology().getClient(ehcacheClientIdentifier).get().getContext() .with("cacheManagerName", "my-super-cache-manager"); nmsService.startStatisticCollector(ehcacheClient, 1, TimeUnit.SECONDS).waitForReturn(); } protected static List<ContextualStatistics> waitForNextStats() throws Exception { // uses the monitoring consumre entity to get the content of the stat buffer when some stats are collected return nmsService.waitForMessage(message -> message.getType().equals("STATISTICS")) .stream() .filter(message -> message.getType().equals("STATISTICS")) .flatMap(message -> message.unwrap(ContextualStatistics.class).stream()) .collect(Collectors.toList()); } protected static List<String> notificationTypes(List<Message> messages) { return messages .stream() .filter(message -> "NOTIFICATION".equals(message.getType())) .flatMap(message -> message.unwrap(ContextualNotification.class).stream()) .map(ContextualNotification::getType) .collect(Collectors.toList()); } protected static String read(String path) throws FileNotFoundException { Scanner scanner = new Scanner(AbstractClusteringManagementTest.class.getResourceAsStream(path), "UTF-8"); try { return scanner.useDelimiter("\\A").next(); } finally { scanner.close(); } } protected static String normalizeForLineEndings(String stringToNormalize) { return stringToNormalize.replace("\r\n", "\n").replace("\r", "\n"); } private static void sendManagementCallOnEntityToCollectStats() throws Exception { Context context = readTopology().getSingleStripe().getActiveServerEntity(tmsServerEntityIdentifier).get().getContext(); nmsService.startStatisticCollector(context, 1, TimeUnit.SECONDS).waitForReturn(); } protected static List<ContextualNotification> waitForAllNotifications(String... notificationTypes) throws InterruptedException { List<String> waitingFor = new ArrayList<>(Arrays.asList(notificationTypes)); return nmsService.waitForMessage(message -> { if (message.getType().equals("NOTIFICATION")) { for (ContextualNotification notification : message.unwrap(ContextualNotification.class)) { waitingFor.remove(notification.getType()); } } return waitingFor.isEmpty(); }).stream() .filter(message -> message.getType().equals("NOTIFICATION")) .flatMap(message -> message.unwrap(ContextualNotification.class).stream()) .collect(Collectors.toList()); } }