// This file is part of OpenTSDB. // Copyright (C) 2018 The OpenTSDB Authors. // // 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 net.opentsdb.tsd; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import kafka.consumer.Consumer; import kafka.consumer.ConsumerConfig; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.consumer.TopicFilter; import kafka.javaapi.consumer.ConsumerConnector; import kafka.message.MessageAndMetadata; import net.opentsdb.core.HistogramCodecManager; import net.opentsdb.core.IncomingDataPoint; import net.opentsdb.core.TSDB; import net.opentsdb.data.Aggregate; import net.opentsdb.data.Histogram; import net.opentsdb.data.Metric; import net.opentsdb.data.TypedIncomingData; import net.opentsdb.data.deserializers.Deserializer; import net.opentsdb.data.deserializers.JSONDeserializer; import net.opentsdb.tsd.KafkaRpcPluginGroup.TsdbConsumerType; import net.opentsdb.utils.Config; import net.opentsdb.utils.JSON; import org.hbase.async.HBaseException; import org.hbase.async.PleaseThrottleException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.RateLimiter; import com.stumbleupon.async.Deferred; @PowerMockIgnore({ "javax.management.*" }) @RunWith(PowerMockRunner.class) @PrepareForTest({ TSDB.class, KafkaRpcPluginThread.class, Config.class, Thread.class, Consumer.class, ConsumerConfig.class, PleaseThrottleException.class}) public class TestKafkaRpcPluginThread { private static final String METRIC = "sys.cpu.user"; private static final String METRIC2 = "sys.cpu.iowait"; private static final String PREFIX = "sys"; private static final long TS = 1492641000L; private static final String LOCALHOST = "localhost"; private static final String TOPICS = "TSDB_600_1,TSDB_600_2,TSDB_600_3"; private static final String GROUPID = "testGroup"; private static final String ZKS = "192.168.1.1:2181"; private Map<String, String> TAGS = ImmutableMap.<String, String>builder() .put("host", "web01") .build(); private TSDB tsdb; private KafkaRpcPluginConfig config; private KafkaRpcPluginGroup group; private ConsumerConnector consumer_connector; private ConsumerIterator<byte[], byte[]> iterator; private List<KafkaStream<byte[], byte[]>> stream_list; private MessageAndMetadata<byte[], byte[]> message; private RateLimiter rate_limiter; private TypedIncomingData data; private KafkaRpcPlugin parent; private KafkaStorageExceptionHandler requeue; private ConcurrentMap<String, Map<String, AtomicLong>> counters; private Deserializer deserializer; @SuppressWarnings("unchecked") @Before public void before() throws Exception { tsdb = PowerMockito.mock(TSDB.class); config = new KafkaRpcPluginConfig(new Config(false)); group = mock(KafkaRpcPluginGroup.class); message = mock(MessageAndMetadata.class); rate_limiter = mock(RateLimiter.class); requeue = mock(KafkaStorageExceptionHandler.class); counters = new ConcurrentHashMap<String, Map<String, AtomicLong>>(); deserializer = new JSONDeserializer(); consumer_connector = mock(ConsumerConnector.class); mockStatic(Consumer.class); when(Consumer.createJavaConsumerConnector((ConsumerConfig) any())) .thenReturn(consumer_connector); when(tsdb.getConfig()).thenReturn(config); when(tsdb.getStorageExceptionHandler()).thenReturn(requeue); parent = mock(KafkaRpcPlugin.class); when(parent.getHost()).thenReturn(LOCALHOST); when(parent.getTSDB()).thenReturn(tsdb); when(parent.getConfig()).thenReturn(config); when(parent.getNamespaceCounters()).thenReturn(counters); when(parent.trackMetricPrefix()).thenReturn(true); when(group.getParent()).thenReturn(parent); when(group.getRateLimiter()).thenReturn(rate_limiter); when(group.getGroupID()).thenReturn(GROUPID); when(group.getConsumerType()).thenReturn(TsdbConsumerType.RAW); when(group.getDeserializer()).thenReturn(deserializer); config.overrideConfig(KafkaRpcPluginConfig.KAFKA_CONFIG_PREFIX + "zookeeper.connect", ZKS); stream_list = mock(List.class); when(consumer_connector.createMessageStreamsByFilter( (TopicFilter) any(), anyInt())).thenReturn(stream_list); final KafkaStream<byte[], byte[]> stream = mock(KafkaStream.class); when(stream_list.get(0)).thenReturn(stream); iterator = mock(ConsumerIterator.class); when(stream.iterator()).thenReturn(iterator); when(iterator.hasNext()).thenReturn(true).thenReturn(false); when(iterator.next()).thenReturn(message); PowerMockito.mockStatic(ConsumerConfig.class); PowerMockito.whenNew(ConsumerConfig.class).withAnyArguments() .thenReturn(mock(ConsumerConfig.class)); PowerMockito.mockStatic(Consumer.class); when(Consumer.createJavaConsumerConnector(any(ConsumerConfig.class))) .thenReturn(consumer_connector); } @Test public void ctor() throws Exception { final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); assertEquals(1, writer.threadID()); assertEquals(GROUPID + "_1_" + LOCALHOST, writer.toString()); assertNull(writer.consumer()); assertEquals(0, writer.requeueDelay()); } @Test(expected = NullPointerException.class) public void ctorNullGroup() throws Exception { new KafkaRpcPluginThread(null, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorNullTSDB() throws Exception { when(parent.getTSDB()).thenReturn(null); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorNullRateLimiter() throws Exception { when(group.getRateLimiter()).thenReturn(null); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorNullGroupID() throws Exception { when(group.getGroupID()).thenReturn(null); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorEmptyGroupID() throws Exception { when(group.getGroupID()).thenReturn(""); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorNullHost() throws Exception { when(parent.getHost()).thenReturn(null); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorEmptyHost() throws Exception { when(parent.getHost()).thenReturn(""); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test(expected = IllegalArgumentException.class) public void ctorNullTopics() throws Exception { new KafkaRpcPluginThread(group, 1, null); } @Test(expected = IllegalArgumentException.class) public void ctorEmptyTopics() throws Exception { new KafkaRpcPluginThread(group, 1, ""); } @Test(expected = IllegalArgumentException.class) public void ctorNegativeThreadID() throws Exception { new KafkaRpcPluginThread(group, -1, TOPICS); } @Test public void ctorRequeue() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_RAW); final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); assertEquals(writer.threadID(), 1); assertNull(writer.consumer()); assertEquals(KafkaRpcPluginConfig.DEFAULT_REQUEUE_DELAY_MS, writer.requeueDelay()); } @Test public void ctorRequeueOverride() throws Exception { config.overrideConfig(KafkaRpcPluginConfig.PLUGIN_PROPERTY_BASE + "requeueDelay", "42"); when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_RAW); final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); assertEquals(writer.threadID(), 1); assertNull(writer.consumer()); assertEquals(42, writer.requeueDelay()); } @Test (expected = IllegalArgumentException.class) public void ctorRequeueOverrideBadValue() throws Exception { config.overrideConfig(KafkaRpcPluginConfig.PLUGIN_PROPERTY_BASE + "requeueDelay", "notanumber"); when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_RAW); new KafkaRpcPluginThread(group, 1, TOPICS); } @Test public void buildConsumerPropertiesDefaults() throws Exception { final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); final Properties props = writer.buildConsumerProperties(); assertEquals(GROUPID, props.get(KafkaRpcPluginThread.GROUP_ID)); assertEquals(Integer.toString(1) + "_" + LOCALHOST, props.get(KafkaRpcPluginThread.CONSUMER_ID)); assertEquals( Integer.toString(KafkaRpcPluginConfig.AUTO_COMMIT_INTERVAL_DEFAULT), props.get(KafkaRpcPluginConfig.AUTO_COMMIT_INTERVAL_MS)); assertEquals(KafkaRpcPluginConfig.AUTO_COMMIT_ENABLE_DEFAULT, props.get(KafkaRpcPluginConfig.AUTO_COMMIT_ENABLE)); assertEquals(KafkaRpcPluginConfig.AUTO_OFFSET_RESET_DEFAULT, props.get(KafkaRpcPluginConfig.AUTO_OFFSET_RESET)); assertEquals( Integer.toString(KafkaRpcPluginConfig.REBALANCE_BACKOFF_MS_DEFAULT), props.get(KafkaRpcPluginConfig.REBALANCE_BACKOFF_MS)); assertEquals( Integer.toString(KafkaRpcPluginConfig.REBALANCE_RETRIES_DEFAULT), props.get(KafkaRpcPluginConfig.REBALANCE_RETRIES)); assertEquals( Integer.toString(KafkaRpcPluginConfig.ZK_SESSION_TIMEOUT_DEFAULT), props.get(KafkaRpcPluginConfig.ZOOKEEPER_SESSION_TIMEOUT_MS)); assertEquals( Integer.toString(KafkaRpcPluginConfig.ZK_CONNECTION_TIMEOUT_DEFAULT), props.get(KafkaRpcPluginConfig.ZOOKEEPER_CONNECTION_TIMEOUT_MS)); assertEquals(ZKS, props.get("zookeeper.connect")); } @Test public void buildConsumerPropertiesGroupOverride() throws Exception { config.overrideConfig(KafkaRpcPluginConfig.KAFKA_CONFIG_PREFIX + GROUPID + "." + "zookeeper.connect", "10.0.0.1:2181"); // make sure a different group doesn't mess us up config.overrideConfig(KafkaRpcPluginConfig.KAFKA_CONFIG_PREFIX + "diffGroup." + KafkaRpcPluginConfig.AUTO_COMMIT_INTERVAL_MS, "1024"); final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); final Properties props = writer.buildConsumerProperties(); assertEquals(GROUPID, props.get(KafkaRpcPluginThread.GROUP_ID)); assertEquals(Integer.toString(1) + "_" + LOCALHOST, props.get(KafkaRpcPluginThread.CONSUMER_ID)); assertEquals( Integer.toString(KafkaRpcPluginConfig.AUTO_COMMIT_INTERVAL_DEFAULT), props.get(KafkaRpcPluginConfig.AUTO_COMMIT_INTERVAL_MS)); assertEquals(KafkaRpcPluginConfig.AUTO_COMMIT_ENABLE_DEFAULT, props.get(KafkaRpcPluginConfig.AUTO_COMMIT_ENABLE)); assertEquals(KafkaRpcPluginConfig.AUTO_OFFSET_RESET_DEFAULT, props.get(KafkaRpcPluginConfig.AUTO_OFFSET_RESET)); assertEquals( Integer.toString(KafkaRpcPluginConfig.REBALANCE_BACKOFF_MS_DEFAULT), props.get(KafkaRpcPluginConfig.REBALANCE_BACKOFF_MS)); assertEquals( Integer.toString(KafkaRpcPluginConfig.REBALANCE_RETRIES_DEFAULT), props.get(KafkaRpcPluginConfig.REBALANCE_RETRIES)); assertEquals( Integer.toString(KafkaRpcPluginConfig.ZK_SESSION_TIMEOUT_DEFAULT), props.get(KafkaRpcPluginConfig.ZOOKEEPER_SESSION_TIMEOUT_MS)); assertEquals( Integer.toString(KafkaRpcPluginConfig.ZK_CONNECTION_TIMEOUT_DEFAULT), props.get(KafkaRpcPluginConfig.ZOOKEEPER_CONNECTION_TIMEOUT_MS)); assertEquals("10.0.0.1:2181", props.get("zookeeper.connect")); } @Test public void buildConsumerConnector() throws Exception { final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); assertNotNull(writer.buildConsumerConnector()); } @Test public void shutdown() throws Exception { final KafkaRpcPluginThread writer = new KafkaRpcPluginThread(group, 1, TOPICS); writer.run(); writer.shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test public void runNoData() throws Exception { when(iterator.hasNext()).thenReturn(false); final KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(tsdb, never()).addPoint(anyString(), anyLong(), anyLong(), anyMap()); verify(tsdb, never()).addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap()); verify(tsdb, never()).addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString()); verify(consumer_connector, times(1)) .createMessageStreamsByFilter(any(TopicFilter.class), anyInt()); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test public void runNoDataRestart() throws Exception { when(iterator.hasNext()).thenReturn(false); final KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); writer.run(); verify(tsdb, never()).addPoint(anyString(), anyLong(), anyLong(), anyMap()); verify(tsdb, never()).addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap()); verify(tsdb, never()).addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString()); verify(consumer_connector, times(2)) .createMessageStreamsByFilter(any(TopicFilter.class), anyInt()); verify(writer, times(2)).shutdown(); verify(consumer_connector, times(2)).shutdown(); } @Test public void runNoStreams() throws Exception { when(stream_list.get(0)) .thenThrow(new ArrayIndexOutOfBoundsException()); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(tsdb, never()).addPoint(anyString(), anyLong(), anyLong(), anyMap()); verify(tsdb, never()).addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap()); verify(tsdb, never()).addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString()); verify(consumer_connector, times(1)) .createMessageStreamsByFilter(any(TopicFilter.class), anyInt()); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test public void runGoodMessageRaw() throws Exception { setupRawData(false); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRawCounter", "storedRawCounter" }); } @Test public void runGoodMessageRawList() throws Exception { setupRawDataList(false); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verify(tsdb, times(1)).addPoint(METRIC2, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRawCounter", "storedRawCounter" }, 2); } @Test public void runGoodMessageRollup() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.ROLLUP); setupAggData(false, false); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addAggregatePoint(METRIC, TS, 42L, TAGS, false, "1h", "sum", null); verifyCtrsInc(new String[]{ "readRollupCounter", "storedRollupCounter" }); } @Test public void runGoodMessagePreAgg() throws Exception { setupAggData(false, true); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addAggregatePoint(METRIC, TS, 42L, TAGS, true, null, null, "sum"); verifyCtrsInc(new String[]{ "readAggregateCounter", "storedAggregateCounter" }); } @Test public void runGoodMessageHistogram() throws Exception { setupHistogramData(false); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addHistogramPoint(eq(METRIC), eq(TS), any(byte[].class), eq(TAGS)); verifyCtrsInc(new String[]{ "readHistogramCounter", "storedHistogramCounter" }); } @Test public void runGoodMessageRequeueRaw() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_RAW); setupRawData(true); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRequeueRawCounter", "storedRequeueRawCounter" }); } @Test public void runGoodMessageRequeueRawList() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_RAW); setupRawDataList(true); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verify(tsdb, times(1)).addPoint(METRIC2, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRequeueRawCounter", "storedRequeueRawCounter" }, 2); } @Test public void runGoodMessageRequeueRollup() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_ROLLUP); setupAggData(true, false); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addAggregatePoint(METRIC, TS, 42L, TAGS, false, "1h", "sum", null); verifyCtrsInc(new String[]{ "readRequeueRollupCounter", "storedRequeueRollupCounter" }); } @Test public void runGoodMessageRequeuePreAgg() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.REQUEUE_ROLLUP); setupAggData(true, true); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addAggregatePoint(METRIC, TS, 42L, TAGS, true, null, null, "sum"); verifyCtrsInc(new String[]{ "readRequeueAggregateCounter", "storedRequeueAggregateCounter" }); } @Test public void runGoodMessageRequeueHistogram() throws Exception { setupHistogramData(true); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addHistogramPoint(eq(METRIC), eq(TS), any(byte[].class), eq(TAGS)); verifyCtrsInc(new String[]{ "readRequeueHistogramCounter", "storedRequeueHistogramCounter" }); } @Test public void runEmptyData() throws Exception { when(message.message()).thenReturn(new byte[] { '{', '}' }); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(tsdb, never()).addPoint(anyString(), anyLong(), anyLong(), anyMap()); verify(tsdb, never()).addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap()); verify(tsdb, never()).addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString()); verifyMessageRead(writer, false); } @Test public void runEmptyMetric() throws Exception { when(tsdb.addPoint(anyString(), anyLong(), anyLong(), anyMap())) .thenReturn(Deferred.fromResult(null)); data = new Metric(null, TS, "42", TAGS); when(message.message()).thenReturn(JSON.serializeToBytes(data)); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(tsdb, never()).addPoint(anyString(), anyLong(), anyLong(), anyMap()); verify(tsdb, never()).addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap()); verify(tsdb, never()).addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString()); verifyMessageRead(writer, false); } @Test public void runStorageFailure() throws Exception { setupRawData(false); when(tsdb.addPoint(anyString(), anyLong(), anyLong(), anyMap())) .thenReturn(Deferred.fromResult(mock(HBaseException.class))); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, true); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRawCounter", "requeuedRawCounter", "storageExceptionCounter" }); } @Test public void runStorageFailureRollup() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.ROLLUP); setupAggData(false, false); when(tsdb.addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString())) .thenReturn(Deferred.fromResult(mock(HBaseException.class))); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, true); verify(tsdb, times(1)).addAggregatePoint(METRIC, TS, 42L, TAGS, false, "1h", "sum", null); verifyCtrsInc(new String[]{ "readRollupCounter", "requeuedRollupCounter", "storageExceptionCounter" }); } @Test public void runStorageFailurePreAgg() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.ROLLUP); setupAggData(false, true); when(tsdb.addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString())) .thenReturn(Deferred.fromResult(mock(HBaseException.class))); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, true); verify(tsdb, times(1)).addAggregatePoint(METRIC, TS, 42L, TAGS, true, null, null, "sum"); verifyCtrsInc(new String[]{ "readAggregateCounter", "requeuedAggregateCounter", "storageExceptionCounter" }); } @Test public void runStorageFailureHistogram() throws Exception { when(group.getConsumerType()).thenReturn(TsdbConsumerType.ROLLUP); setupHistogramData(false); when(tsdb.addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap())) .thenReturn(Deferred.fromResult(mock(HBaseException.class))); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, true); verify(tsdb, times(1)).addHistogramPoint(eq(METRIC), eq(TS), any(byte[].class), eq(TAGS)); verifyCtrsInc(new String[]{ "readHistogramCounter", "requeuedHistogramCounter", "storageExceptionCounter" }); } @SuppressWarnings("unchecked") @Test public void runRequeueWTF() throws Exception { setupRawData(false); when(tsdb.addPoint(anyString(), anyLong(), anyLong(), anyMap())) .thenThrow(new RuntimeException("Boo!")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, false); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRawCounter", "exceptionCounter" }); } @SuppressWarnings("unchecked") @Test public void runRequeueThrottling() throws Exception { setupRawData(false); when(tsdb.addPoint(anyString(), anyLong(), anyLong(), anyMap())) .thenReturn(Deferred.fromResult(mock(PleaseThrottleException.class))); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verifyMessageRead(writer, true); verify(tsdb, times(1)).addPoint(METRIC, TS, 42L, TAGS); verifyCtrsInc(new String[]{ "readRawCounter", "requeuedRawCounter", "pleaseThrottleExceptionCounter" }); } @Test public void runTooManyRuntimeException() throws Exception { when(iterator.hasNext()).thenReturn(true); when(iterator.next()).thenThrow(new RuntimeException("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test public void runIteratorHasNextRuntimeException() throws Exception { when(iterator.hasNext()).thenThrow(new RuntimeException("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test(expected = Exception.class) public void runIteratorHasNextException() throws Exception { when(iterator.hasNext()).thenThrow(new Exception("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); } @Test public void runIteratorNextRuntimeException() throws Exception { when(iterator.next()).thenThrow(new RuntimeException("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test(expected = Exception.class) public void runIteratorNextException() throws Exception { when(iterator.next()).thenThrow(new Exception("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); } @Test public void runConsumerRuntimeException() throws Exception { when(consumer_connector.createMessageStreamsByFilter( (TopicFilter) any(), anyInt())).thenThrow( new RuntimeException("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } @Test(expected = Exception.class) public void runConsumerException() throws Exception { when(consumer_connector.createMessageStreamsByFilter( (TopicFilter) any(), anyInt())).thenThrow( new Exception("Foobar")); KafkaRpcPluginThread writer = Mockito.spy( new KafkaRpcPluginThread(group, 1, TOPICS)); writer.run(); verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); } // ------ HELPERS --------- private void setupRawData(final boolean requeued) { when(tsdb.addPoint(anyString(), anyLong(), anyLong(), anyMap())) .thenReturn(Deferred.fromResult(null)); data = new Metric(METRIC, TS, "42", TAGS); if (requeued) { data.setRequeueTS(TS + 60); } when(message.message()).thenReturn(JSON.serializeToBytes(data)); } private void setupRawDataList(final boolean requeued) { when(tsdb.addPoint(anyString(), anyLong(), anyLong(), anyMap())) .thenReturn(Deferred.fromResult(null)); TypedIncomingData ev1 = new Metric(METRIC, TS, "42", TAGS); TypedIncomingData ev2 = new Metric(METRIC2, TS, "42", TAGS); if (requeued) { ev1.setRequeueTS(TS + 60); ev2.setRequeueTS(TS + 60); } StringBuilder sb = new StringBuilder(); sb.append("["); sb.append(new String(JSON.serializeToBytes(ev1))); sb.append(","); sb.append(new String(JSON.serializeToBytes(ev2))); sb.append("]"); when(message.message()).thenReturn(sb.toString().getBytes()); } private void setupAggData(final boolean requeued, final boolean is_group_by) { when(tsdb.addAggregatePoint(anyString(), anyLong(), anyLong(), anyMap(), anyBoolean(), anyString(), anyString(), anyString())) .thenReturn(Deferred.fromResult(null)); if (is_group_by) { data = new Aggregate(METRIC, TS, "42", TAGS, null, null, "sum"); } else { data = new Aggregate(METRIC, TS, "42", TAGS, "1h", "sum", null); } if (requeued) { data.setRequeueTS(TS + 60); } when(message.message()).thenReturn(JSON.serializeToBytes(data)); } private void setupHistogramData(final boolean requeued) { config.overrideConfig("tsd.core.histograms.config", "{\"net.opentsdb.core.SimpleHistogramDecoder\": 42}"); final HistogramCodecManager manager; manager = new HistogramCodecManager(tsdb); when(tsdb.histogramManager()).thenReturn(manager); when(tsdb.addHistogramPoint(anyString(), anyLong(), any(byte[].class), anyMap())) .thenReturn(Deferred.fromResult(null)); final Histogram histo = new Histogram(); histo.setMetric(METRIC); histo.setTimestamp(TS); histo.setTags(new HashMap<String, String>(TAGS)); histo.setOverflow(1); histo.setBuckets(ImmutableMap.<String, Long>builder() .put("0,1", 42L) .put("1,5", 24L) .build()); data = histo; if (requeued) { data.setRequeueTS(TS + 60); } when(message.message()).thenReturn(JSON.serializeToBytes(data)); } /** * Runs through the list of types for the namespace and expects them to * be 1. Any other CounterTypes should be zero. Uses default test NAMESPACE * @param types The types to look for */ private void verifyCtrsInc(final String[] types) { verifyCtrsInc(types, PREFIX, 1); } /** * Runs through the list of types for the namespace and expects them to * be 1. Any other CounterTypes should be zero. Uses default test NAMESPACE * @param types The types to look for * @param expectedCount Expected count (to be used for list message processing) */ private void verifyCtrsInc(final String[] types, Integer expectedCount) { verifyCtrsInc(types, PREFIX, expectedCount); } /** * Runs through the list of types for the namespace and expects them to * be 1. Any other CounterTypes should be zero. * @param types The types to look for * @param namespace A namespace to look for */ private void verifyCtrsInc(final String[] types, final String namespace) { verifyCtrsInc(types, namespace, 1); } /** * Runs through the list of types for the namespace and expects them to * be 1. Any other CounterTypes should be zero. * @param types The types to look for * @param namespace A namespace to look for * @param expectedCount Expected count (to be used for list message processing) */ private void verifyCtrsInc(final String[] types, final String namespace, final Integer expectedCount) { for (final String type : types) { if (counters.get(type) == null) { throw new AssertionError("No ns map for type [" + type + "]"); } if (counters.get(type).get(namespace) == null) { throw new AssertionError("No counter for type [" + type + "] and ns [" + namespace + "]"); } if (counters.get(type).get(namespace).get() != expectedCount) { throw new AssertionError("Counter was not zero for [" + type + "] and ns [" + namespace + "]"); } } verifyCtrsZero(types, namespace); } /** * Runs through the whole counter map and fails on any that are not in the * skip list. * @param types The types to skip * @param namespace A namespace to look for */ private void verifyCtrsZero(final String[] types, final String namespace) { for (final Entry<String, Map<String, AtomicLong>> type_map : counters.entrySet()) { boolean unexpected_type = true; for (final String type : types) { if (type_map.getKey().equals(type)) { for (final Entry<String, AtomicLong> ns : type_map.getValue() .entrySet()) { if (!ns.getKey().equals(namespace)) { throw new AssertionError("Found a non-zero counter for type [" + type + "] and ns [" + ns.getKey() + "]"); } } unexpected_type = false; } } if (unexpected_type) { throw new AssertionError("Found a non-zero counter for type [" + type_map.getKey() + "]"); } } } private void verifyMessageRead(final KafkaRpcPluginThread writer, final boolean requeued) { verify(writer, times(1)).shutdown(); verify(consumer_connector, times(1)).shutdown(); verify(consumer_connector, times(1)) .createMessageStreamsByFilter(any(TopicFilter.class), anyInt()); verify(iterator, times(2)).hasNext(); if (requeued) { verify(requeue, times(1)).handleError( any(IncomingDataPoint.class), any(Exception.class)); } else { verify(requeue, never()).handleError( any(IncomingDataPoint.class), any(Exception.class)); } if (data != null) { verify(rate_limiter, times(1)).acquire(); } } }