Repository: ambari Updated Branches: refs/heads/branch-2.5 ae30013da -> 4d6a71b79
AMBARI-20819. LogSearch Integration should limit requests to portal for missing components. (rnettleton) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/4d6a71b7 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/4d6a71b7 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/4d6a71b7 Branch: refs/heads/branch-2.5 Commit: 4d6a71b799a16c719233c40274817694ac0e0312 Parents: ae30013 Author: Bob Nettleton <[email protected]> Authored: Mon Apr 24 11:49:11 2017 -0400 Committer: Bob Nettleton <[email protected]> Committed: Mon Apr 24 11:49:11 2017 -0400 ---------------------------------------------------------------------- .../logging/LogSearchDataRetrievalService.java | 75 ++++-- .../LogSearchDataRetrievalServiceTest.java | 259 ++++++++++++++++++- 2 files changed, 304 insertions(+), 30 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/4d6a71b7/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalService.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalService.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalService.java index b227aac..2ca20cf 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalService.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalService.java @@ -17,10 +17,12 @@ */ package org.apache.ambari.server.controller.logging; +import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.ambari.server.AmbariService; import org.apache.ambari.server.configuration.Configuration; @@ -30,9 +32,9 @@ import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.AbstractService; import com.google.inject.Inject; @@ -68,6 +70,13 @@ public class LogSearchDataRetrievalService extends AbstractService { private static Logger LOG = LoggerFactory.getLogger(LogSearchDataRetrievalService.class); /** + * Maximum number of failed attempts that the LogSearch integration code will attempt for + * a given component before treating the component as failed and skipping the request. + * + */ + private static int MAX_RETRIES_FOR_FAILED_METADATA_REQUEST = 10; + + /** * Factory instance used to handle URL string generation requests on the * main request thread. */ @@ -108,6 +117,19 @@ public class LogSearchDataRetrievalService extends AbstractService { */ private final Set<String> currentRequests = Sets.newConcurrentHashSet(); + /** + * A map that maintains the set of failure counts for logging + * metadata requests on a per-component basis. This map should + * be consulted prior to making a metadata request to the LogSearch + * service. If LogSearch has already returned an empty list for the given + * component, or any other error has occurred for a certain number of attempts, + * the request should not be attempted further. + * + */ + private final Map<String, AtomicInteger> componentRequestFailureCounts = + Maps.newConcurrentMap(); + + /** * Executor instance to be used to run REST queries against @@ -171,18 +193,20 @@ public class LogSearchDataRetrievalService extends AbstractService { LOG.debug("LogFileNames result for key = {} found in cache", key); return cacheResult; } else { - // queue up a thread to create the LogSearch REST request to obtain this information - if (currentRequests.contains(key)) { - LOG.debug("LogFileNames request has been made for key = {}, but not completed yet", key); + if (!componentRequestFailureCounts.containsKey(component) || componentRequestFailureCounts.get(component).get() < MAX_RETRIES_FOR_FAILED_METADATA_REQUEST) { + // queue up a thread to create the LogSearch REST request to obtain this information + if (currentRequests.contains(key)) { + LOG.debug("LogFileNames request has been made for key = {}, but not completed yet", key); + } else { + LOG.debug("LogFileNames result for key = {} not in cache, queueing up remote request", key); + // add request key to queue, to keep multiple copies of the same request from + // being submitted + currentRequests.add(key); + startLogSearchFileNameRequest(host, component, cluster); + } } else { - LOG.debug("LogFileNames result for key = {} not in cache, queueing up remote request", key); - // add request key to queue, to keep multiple copies of the same request from - // being submitted - currentRequests.add(key); - startLogSearchFileNameRequest(host, component, cluster); + LOG.debug("Too many failures occurred while attempting to obtain log file metadata for component = {}, Ambari will ignore this component for LogSearch Integration", component); } - - } return null; @@ -259,6 +283,15 @@ public class LogSearchDataRetrievalService extends AbstractService { return currentRequests; } + /** + * This protected method allows for simpler unit tests. + * + * @return the Map of failure counts on a per-component basis + */ + protected Map<String, AtomicInteger> getComponentRequestFailureCounts() { + return componentRequestFailureCounts; + } + private void startLogSearchFileNameRequest(String host, String component, String cluster) { // Create a separate instance of LoggingRequestHelperFactory for // each task launched, since these tasks will occur on a separate thread @@ -267,7 +300,7 @@ public class LogSearchDataRetrievalService extends AbstractService { // TODO: the LoggingRequestHelperFactory implementation thread-safe, so that // TODO: a single factory instance can be shared across multiple threads safely executor.execute(new LogSearchFileNameRequestRunnable(host, component, cluster, logFileNameCache, currentRequests, - injector.getInstance(LoggingRequestHelperFactory.class))); + injector.getInstance(LoggingRequestHelperFactory.class), componentRequestFailureCounts)); } private AmbariManagementController getController() { @@ -303,20 +336,24 @@ public class LogSearchDataRetrievalService extends AbstractService { private LoggingRequestHelperFactory loggingRequestHelperFactory; + private final Map<String, AtomicInteger> componentRequestFailureCounts; + private AmbariManagementController controller; - LogSearchFileNameRequestRunnable(String host, String component, String cluster, Cache<String, Set<String>> logFileNameCache, Set<String> currentRequests, LoggingRequestHelperFactory loggingRequestHelperFactory) { - this(host, component, cluster, logFileNameCache, currentRequests, loggingRequestHelperFactory, AmbariServer.getController()); + LogSearchFileNameRequestRunnable(String host, String component, String cluster, Cache<String, Set<String>> logFileNameCache, Set<String> currentRequests, LoggingRequestHelperFactory loggingRequestHelperFactory, + Map<String, AtomicInteger> componentRequestFailureCounts) { + this(host, component, cluster, logFileNameCache, currentRequests, loggingRequestHelperFactory, componentRequestFailureCounts, AmbariServer.getController()); } LogSearchFileNameRequestRunnable(String host, String component, String cluster, Cache<String, Set<String>> logFileNameCache, Set<String> currentRequests, - LoggingRequestHelperFactory loggingRequestHelperFactory, AmbariManagementController controller) { + LoggingRequestHelperFactory loggingRequestHelperFactory, Map<String, AtomicInteger> componentRequestFailureCounts, AmbariManagementController controller) { this.host = host; this.component = component; this.cluster = cluster; this.logFileNameCache = logFileNameCache; this.currentRequests = currentRequests; this.loggingRequestHelperFactory = loggingRequestHelperFactory; + this.componentRequestFailureCounts = componentRequestFailureCounts; this.controller = controller; } @@ -339,7 +376,13 @@ public class LogSearchDataRetrievalService extends AbstractService { // update cache with returned result logFileNameCache.put(key, logFileNamesResult); } else { - LOG.debug("LogSearchFileNameRequestRunnable: remote request was not successful"); + LOG.debug("LogSearchFileNameRequestRunnable: remote request was not successful for component = {} on host ={}", component, host); + if (!componentRequestFailureCounts.containsKey(component)) { + componentRequestFailureCounts.put(component, new AtomicInteger()); + } + + // increment the failure count for this component + componentRequestFailureCounts.get(component).incrementAndGet(); } } else { LOG.debug("LogSearchFileNameRequestRunnable: request helper was null. This may mean that LogSearch is not available, or could be a potential connection problem."); http://git-wip-us.apache.org/repos/asf/ambari/blob/4d6a71b7/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalServiceTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalServiceTest.java index 2adaea3..56c225c 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalServiceTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/logging/LogSearchDataRetrievalServiceTest.java @@ -17,35 +17,32 @@ */ package org.apache.ambari.server.controller.logging; +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.isA; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.Collections; +import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; - +import java.util.concurrent.atomic.AtomicInteger; import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.controller.AmbariManagementController; +import org.easymock.Capture; +import org.easymock.EasyMock; import org.easymock.EasyMockSupport; import org.junit.Test; import com.google.common.cache.Cache; import com.google.inject.Injector; - - -import org.apache.ambari.server.controller.AmbariManagementController; -import org.easymock.EasyMockSupport; -import org.junit.Test; - -import com.google.common.cache.Cache; - - /** * This test verifies the basic behavior of the * LogSearchDataRetrievalServiceTest, and should @@ -176,6 +173,161 @@ public class LogSearchDataRetrievalServiceTest { assertTrue("Incorrect HostComponent set on request set", retrievalService.getCurrentRequests().contains(expectedComponentName + "+" + expectedHostName)); + assertEquals("Incorrect size for failure counts for components, should be 0", + 0, retrievalService.getComponentRequestFailureCounts().size()); + + mockSupport.verifyAll(); + } + + @Test + public void testGetLogFileNamesExistingFailuresLessThanThreshold() throws Exception { + final String expectedHostName = "c6401.ambari.apache.org"; + final String expectedComponentName = "DATANODE"; + final String expectedClusterName = "clusterone"; + + EasyMockSupport mockSupport = new EasyMockSupport(); + + LoggingRequestHelperFactory helperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); + + Executor executorMock = mockSupport.createMock(Executor.class); + + Injector injectorMock = + mockSupport.createMock(Injector.class); + + Configuration configurationMock = + mockSupport.createMock(Configuration.class); + + // expect the executor to be called to execute the LogSearch request + executorMock.execute(isA(LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable.class)); + // executor should only be called once + expectLastCall().once(); + + expect(injectorMock.getInstance(LoggingRequestHelperFactory.class)).andReturn(helperFactoryMock); + + expect(configurationMock.getLogSearchMetadataCacheExpireTimeout()).andReturn(1).atLeastOnce(); + + mockSupport.replayAll(); + + LogSearchDataRetrievalService retrievalService = new LogSearchDataRetrievalService(); + retrievalService.setLoggingRequestHelperFactory(helperFactoryMock); + retrievalService.setInjector(injectorMock); + retrievalService.setConfiguration(configurationMock); + // call the initialization routine called by the Google framework + retrievalService.doStart(); + retrievalService.setExecutor(executorMock); + // initialize the comopnent-based failure count to have a count > 0, but less than threshold (10) + retrievalService.getComponentRequestFailureCounts().put(expectedComponentName, new AtomicInteger(5)); + + + assertEquals("Default request set should be empty", 0, retrievalService.getCurrentRequests().size()); + + Set<String> resultSet = retrievalService.getLogFileNames(expectedComponentName, expectedHostName, expectedClusterName); + + assertNull("Inital query on the retrieval service should be null, since cache is empty by default", resultSet); + assertEquals("Incorrect number of entries in the current request set", 1, retrievalService.getCurrentRequests().size()); + + assertTrue("Incorrect HostComponent set on request set", + retrievalService.getCurrentRequests().contains(expectedComponentName + "+" + expectedHostName)); + assertEquals("Incorrect size for failure counts for components, should be 0", + 1, retrievalService.getComponentRequestFailureCounts().size()); + assertEquals("Incorrect failure count for component", + 5, retrievalService.getComponentRequestFailureCounts().get(expectedComponentName).get()); + + mockSupport.verifyAll(); + } + + @Test + public void testGetLogFileNamesExistingFailuresAtThreshold() throws Exception { + final String expectedHostName = "c6401.ambari.apache.org"; + final String expectedComponentName = "DATANODE"; + final String expectedClusterName = "clusterone"; + + EasyMockSupport mockSupport = new EasyMockSupport(); + + LoggingRequestHelperFactory helperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); + + Executor executorMock = mockSupport.createMock(Executor.class); + + Injector injectorMock = + mockSupport.createMock(Injector.class); + + Configuration configurationMock = + mockSupport.createMock(Configuration.class); + + expect(configurationMock.getLogSearchMetadataCacheExpireTimeout()).andReturn(1).atLeastOnce(); + + mockSupport.replayAll(); + + LogSearchDataRetrievalService retrievalService = new LogSearchDataRetrievalService(); + retrievalService.setLoggingRequestHelperFactory(helperFactoryMock); + retrievalService.setInjector(injectorMock); + retrievalService.setConfiguration(configurationMock); + // call the initialization routine called by the Google framework + retrievalService.doStart(); + retrievalService.setExecutor(executorMock); + // initialize the comopnent-based failure count to have a count at the threshold + retrievalService.getComponentRequestFailureCounts().put(expectedComponentName, new AtomicInteger(10)); + + assertEquals("Default request set should be empty", 0, retrievalService.getCurrentRequests().size()); + + Set<String> resultSet = + retrievalService.getLogFileNames(expectedComponentName, expectedHostName, expectedClusterName); + + assertNull("Inital query on the retrieval service should be null, since cache is empty by default", resultSet); + assertEquals("Incorrect number of entries in the current request set", 0, retrievalService.getCurrentRequests().size()); + + assertEquals("Incorrect size for failure counts for components, should be 0", + 1, retrievalService.getComponentRequestFailureCounts().size()); + assertEquals("Incorrect failure count for component", + 10, retrievalService.getComponentRequestFailureCounts().get(expectedComponentName).get()); + + mockSupport.verifyAll(); + } + + @Test + public void testGetLogFileNamesExistingFailuresOverThreshold() throws Exception { + final String expectedHostName = "c6401.ambari.apache.org"; + final String expectedComponentName = "DATANODE"; + final String expectedClusterName = "clusterone"; + + EasyMockSupport mockSupport = new EasyMockSupport(); + + LoggingRequestHelperFactory helperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); + + Executor executorMock = mockSupport.createMock(Executor.class); + + Injector injectorMock = + mockSupport.createMock(Injector.class); + + Configuration configurationMock = + mockSupport.createMock(Configuration.class); + + expect(configurationMock.getLogSearchMetadataCacheExpireTimeout()).andReturn(1).atLeastOnce(); + + mockSupport.replayAll(); + + LogSearchDataRetrievalService retrievalService = new LogSearchDataRetrievalService(); + retrievalService.setLoggingRequestHelperFactory(helperFactoryMock); + retrievalService.setInjector(injectorMock); + retrievalService.setConfiguration(configurationMock); + // call the initialization routine called by the Google framework + retrievalService.doStart(); + retrievalService.setExecutor(executorMock); + // initialize the comopnent-based failure count to have a count over the threshold + retrievalService.getComponentRequestFailureCounts().put(expectedComponentName, new AtomicInteger(20)); + + assertEquals("Default request set should be empty", 0, retrievalService.getCurrentRequests().size()); + + Set<String> resultSet = + retrievalService.getLogFileNames(expectedComponentName, expectedHostName, expectedClusterName); + + assertNull("Inital query on the retrieval service should be null, since cache is empty by default", resultSet); + assertEquals("Incorrect number of entries in the current request set", 0, retrievalService.getCurrentRequests().size()); + + assertEquals("Incorrect size for failure counts for components, should be 0", + 1, retrievalService.getComponentRequestFailureCounts().size()); + assertEquals("Incorrect failure count for component", + 20, retrievalService.getComponentRequestFailureCounts().get(expectedComponentName).get()); mockSupport.verifyAll(); } @@ -235,6 +387,7 @@ public class LogSearchDataRetrievalServiceTest { Cache<String, Set<String>> cacheMock = mockSupport.createMock(Cache.class); Set<String> currentRequestsMock = mockSupport.createMock(Set.class); + Map<String, AtomicInteger> componentFailureCounts = mockSupport.createMock(Map.class); expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(helperMock); expect(helperMock.sendGetLogFileNamesRequest(expectedComponentName, expectedHostName)).andReturn(Collections.singleton("/this/is/just/a/test/directory")); @@ -247,7 +400,7 @@ public class LogSearchDataRetrievalServiceTest { LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable loggingRunnable = new LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable(expectedHostName, expectedComponentName, expectedClusterName, - cacheMock, currentRequestsMock, helperFactoryMock, controllerMock); + cacheMock, currentRequestsMock, helperFactoryMock, componentFailureCounts, controllerMock); loggingRunnable.run(); mockSupport.verifyAll(); @@ -268,6 +421,7 @@ public class LogSearchDataRetrievalServiceTest { Cache<String, Set<String>> cacheMock = mockSupport.createMock(Cache.class); Set<String> currentRequestsMock = mockSupport.createMock(Set.class); + Map<String, AtomicInteger> componentFailureCounts = mockSupport.createMock(Map.class); // return null to simulate an error during helper instance creation expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(null); @@ -279,7 +433,7 @@ public class LogSearchDataRetrievalServiceTest { LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable loggingRunnable = new LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable(expectedHostName, expectedComponentName, expectedClusterName, - cacheMock, currentRequestsMock, helperFactoryMock, controllerMock); + cacheMock, currentRequestsMock, helperFactoryMock, componentFailureCounts, controllerMock); loggingRunnable.run(); mockSupport.verifyAll(); @@ -293,6 +447,7 @@ public class LogSearchDataRetrievalServiceTest { final String expectedComponentName = "DATANODE"; final String expectedClusterName = "clusterone"; final String expectedComponentAndHostName = expectedComponentName + "+" + expectedHostName; + final AtomicInteger testInteger = new AtomicInteger(0); EasyMockSupport mockSupport = new EasyMockSupport(); @@ -302,6 +457,9 @@ public class LogSearchDataRetrievalServiceTest { Cache<String, Set<String>> cacheMock = mockSupport.createMock(Cache.class); Set<String> currentRequestsMock = mockSupport.createMock(Set.class); + Map<String, AtomicInteger> componentFailureCounts = mockSupport.createMock(Map.class); + + Capture<AtomicInteger> captureFailureCount = EasyMock.newCapture(); expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(helperMock); // return null to simulate an error occurring during the LogSearch data request @@ -309,14 +467,72 @@ public class LogSearchDataRetrievalServiceTest { // expect that the completed request is removed from the current request set, // even in the event of a failure to obtain the LogSearch data expect(currentRequestsMock.remove(expectedComponentAndHostName)).andReturn(true).once(); + // expect that the component failure map is initially empty + expect(componentFailureCounts.containsKey(expectedComponentName)).andReturn(false); + // expect that the component map is updated with a new count + expect(componentFailureCounts.put(eq(expectedComponentName), capture(captureFailureCount))).andReturn(new AtomicInteger(0)); + // expect that the runnable will obtain an increment the failure count + expect(componentFailureCounts.get(expectedComponentName)).andReturn(testInteger); + mockSupport.replayAll(); LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable loggingRunnable = new LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable(expectedHostName, expectedComponentName, expectedClusterName, - cacheMock, currentRequestsMock, helperFactoryMock, controllerMock); + cacheMock, currentRequestsMock, helperFactoryMock, componentFailureCounts, controllerMock); loggingRunnable.run(); + assertEquals("Initial count set by Runnable should be 0", + 0, captureFailureCount.getValue().get()); + assertEquals("Failure count should have been incremented", + 1, testInteger.get()); + + mockSupport.verifyAll(); + } + + @Test + @SuppressWarnings("unchecked") + public void testRunnableWithFailedCallNullResultExistingFailureCount() throws Exception { + final String expectedHostName = "c6401.ambari.apache.org"; + final String expectedComponentName = "DATANODE"; + final String expectedClusterName = "clusterone"; + final String expectedComponentAndHostName = expectedComponentName + "+" + expectedHostName; + final AtomicInteger testFailureCount = new AtomicInteger(2); + + EasyMockSupport mockSupport = new EasyMockSupport(); + + LoggingRequestHelperFactory helperFactoryMock = mockSupport.createMock(LoggingRequestHelperFactory.class); + AmbariManagementController controllerMock = mockSupport.createMock(AmbariManagementController.class); + LoggingRequestHelper helperMock = mockSupport.createMock(LoggingRequestHelper.class); + + Cache<String, Set<String>> cacheMock = mockSupport.createMock(Cache.class); + Set<String> currentRequestsMock = mockSupport.createMock(Set.class); + Map<String, AtomicInteger> componentFailureCounts = mockSupport.createMock(Map.class); + + expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(helperMock); + // return null to simulate an error occurring during the LogSearch data request + expect(helperMock.sendGetLogFileNamesRequest(expectedComponentName, expectedHostName)).andReturn(null); + // expect that the completed request is removed from the current request set, + // even in the event of a failure to obtain the LogSearch data + expect(currentRequestsMock.remove(expectedComponentAndHostName)).andReturn(true).once(); + // expect that the component failure map is initially empty + expect(componentFailureCounts.containsKey(expectedComponentName)).andReturn(true); + // expect that the runnable will obtain an increment the existing failure count + expect(componentFailureCounts.get(expectedComponentName)).andReturn(testFailureCount); + + mockSupport.replayAll(); + + assertEquals("Initial count should be 2", + 2, testFailureCount.get()); + + LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable loggingRunnable = + new LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable(expectedHostName, expectedComponentName, expectedClusterName, + cacheMock, currentRequestsMock, helperFactoryMock, componentFailureCounts, controllerMock); + loggingRunnable.run(); + + assertEquals("Failure count should have been incremented", + 3, testFailureCount.get()); + mockSupport.verifyAll(); } @@ -327,6 +543,7 @@ public class LogSearchDataRetrievalServiceTest { final String expectedComponentName = "DATANODE"; final String expectedClusterName = "clusterone"; final String expectedComponentAndHostName = expectedComponentName + "+" + expectedHostName; + final AtomicInteger testInteger = new AtomicInteger(0); EasyMockSupport mockSupport = new EasyMockSupport(); @@ -336,6 +553,9 @@ public class LogSearchDataRetrievalServiceTest { Cache<String, Set<String>> cacheMock = mockSupport.createMock(Cache.class); Set<String> currentRequestsMock = mockSupport.createMock(Set.class); + Map<String, AtomicInteger> componentFailureCounts = mockSupport.createMock(Map.class); + + Capture<AtomicInteger> captureFailureCount = EasyMock.newCapture(); expect(helperFactoryMock.getHelper(controllerMock, expectedClusterName)).andReturn(helperMock); // return null to simulate an error occurring during the LogSearch data request @@ -343,14 +563,25 @@ public class LogSearchDataRetrievalServiceTest { // expect that the completed request is removed from the current request set, // even in the event of a failure to obtain the LogSearch data expect(currentRequestsMock.remove(expectedComponentAndHostName)).andReturn(true).once(); + // expect that the component failure map is initially empty + expect(componentFailureCounts.containsKey(expectedComponentName)).andReturn(false); + // expect that the component map is updated with a new count + expect(componentFailureCounts.put(eq(expectedComponentName), capture(captureFailureCount))).andReturn(new AtomicInteger(0)); + // expect that the runnable will obtain an increment the failure count + expect(componentFailureCounts.get(expectedComponentName)).andReturn(testInteger); mockSupport.replayAll(); LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable loggingRunnable = new LogSearchDataRetrievalService.LogSearchFileNameRequestRunnable(expectedHostName, expectedComponentName, expectedClusterName, - cacheMock, currentRequestsMock, helperFactoryMock, controllerMock); + cacheMock, currentRequestsMock, helperFactoryMock, componentFailureCounts, controllerMock); loggingRunnable.run(); + assertEquals("Initial count set by Runnable should be 0", + 0, captureFailureCount.getValue().get()); + assertEquals("Failure count should have been incremented", + 1, testInteger.get()); + mockSupport.verifyAll(); } }
