/* * Copyright 2014 Rackspace * * 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 com.rackspacecloud.blueflood.inputs.handlers; import com.codahale.metrics.Meter; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.Gson; import com.google.gson.internal.LazilyParsedNumber; import com.netflix.astyanax.serializers.AbstractSerializer; import com.rackspacecloud.blueflood.inputs.formats.AggregatedPayload; import com.rackspacecloud.blueflood.io.Instrumentation; import com.rackspacecloud.blueflood.io.serializers.Serializers; import com.rackspacecloud.blueflood.outputs.formats.ErrorResponse; import com.rackspacecloud.blueflood.outputs.handlers.HandlerTestsBase; import com.rackspacecloud.blueflood.service.Configuration; import com.rackspacecloud.blueflood.service.CoreConfig; import com.rackspacecloud.blueflood.types.*; import com.rackspacecloud.blueflood.utils.DefaultClockImpl; import com.rackspacecloud.blueflood.utils.TimeValue; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; import static com.rackspacecloud.blueflood.TestUtils.getJsonFromFile; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; public class HttpAggregatedIngestionHandlerTest extends HandlerTestsBase { private String payloadJson; private HttpAggregatedIngestionHandler handler; private HttpMetricsIngestionServer.Processor processor; private ChannelHandlerContext context; private Channel channel; private ChannelFuture channelFuture; private Meter ingestedMetrics; private Meter ingestedDelayedMetrics; private final String postfix = ".post"; private static final String TENANT = "tenant"; @Before public void setup() throws Exception { processor = mock(HttpMetricsIngestionServer.Processor.class); handler = new HttpAggregatedIngestionHandler(processor, new TimeValue(5, TimeUnit.SECONDS)); channel = mock(Channel.class); context = mock(ChannelHandlerContext.class); channelFuture = mock(ChannelFuture.class); when(context.channel()).thenReturn(channel); when(channel.write(anyString())).thenReturn(channelFuture); ListenableFuture mockFuture = mock(ListenableFuture.class); when(processor.apply(any(MetricsCollection.class))).thenReturn(mockFuture); when(mockFuture.get(anyLong(), any(TimeUnit.class))).thenReturn(new ArrayList<Boolean>()); ingestedMetrics = Instrumentation.getIngestedMetricsMeter(TENANT); ingestedDelayedMetrics = Instrumentation.getIngestedDelayedMetricsMeter(TENANT); } @Before public void buildPayload() throws IOException { payloadJson = getJsonFromFile("sample_payload.json", postfix); } @Test(expected = NumberFormatException.class) public void testExpectedGsonConversionFailure() { new LazilyParsedNumber("2.321").longValue(); } @Test public void testGsonNumberConversions() { AggregatedPayload payload = AggregatedPayload.create(payloadJson); Number doubleNum = new LazilyParsedNumber("2.321"); assertEquals( Double.parseDouble( "2.321" ), PreaggregateConversions.resolveNumber( doubleNum ) ); Number longNum = new LazilyParsedNumber("12345"); assertEquals(Long.parseLong("12345"), PreaggregateConversions.resolveNumber(longNum)); } @Test public void testCounters() { AggregatedPayload payload = AggregatedPayload.create(payloadJson); Collection<PreaggregatedMetric> counters = PreaggregateConversions.convertCounters("1", 1, 15000, payload.getCounters()); assertEquals( 6, counters.size() ); ensureSerializability(counters); } @Test public void testGauges() { AggregatedPayload payload = AggregatedPayload.create(payloadJson); Collection<PreaggregatedMetric> gauges = PreaggregateConversions.convertGauges("1", 1, payload.getGauges()); assertEquals( 4, gauges.size() ); ensureSerializability(gauges); } @Test public void testSets() { AggregatedPayload payload = AggregatedPayload.create(payloadJson); Collection<PreaggregatedMetric> sets = PreaggregateConversions.convertSets("1", 1, payload.getSets()); assertEquals( 2, sets.size() ); ensureSerializability(sets); } @Test public void testTimers() { AggregatedPayload payload = AggregatedPayload.create(payloadJson); Collection<PreaggregatedMetric> timers = PreaggregateConversions.convertTimers("1", 1, payload.getTimers()); assertEquals( 4, timers.size() ); ensureSerializability(timers); } // ok. while we're out it, let's test serialization. Just for fun. The reasoning is that these metrics // follow a different creation path that what we currently have in tests. private static void ensureSerializability(Collection<PreaggregatedMetric> metrics) { for (PreaggregatedMetric metric : metrics) { AbstractSerializer serializer = Serializers.serializerFor(metric.getMetricValue().getClass()); serializer.toByteBuffer(metric.getMetricValue()); } } @Test public void testEmptyRequest() throws IOException { String requestBody = ""; FullHttpRequest request = createIngestRequest(requestBody); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "Invalid request body", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testEmptyJsonRequest() throws IOException { String requestBody = "{}"; //causes JsonMappingException FullHttpRequest request = createIngestRequest(requestBody); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 3, errorResponse.getErrors().size()); assertEquals("Invalid tenant", "", errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testEmptyTenantId() throws IOException { BluefloodGauge gauge = new BluefloodGauge("gauge.a.b", 5); FullHttpRequest request = createIngestRequest(createRequestBody("", new DefaultClockImpl().now().getMillis(), 0 , new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be empty", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "tenantId", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", "", errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", "", errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testInvalidFlushInterval() throws IOException { BluefloodGauge gauge = new BluefloodGauge("gauge.a.b", 5); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), -1 , new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "must be between 0 and 9223372036854775807", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "flushInterval", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", "", errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testCollectionTimeInPast() throws IOException { BluefloodGauge gauge = new BluefloodGauge("gauge.a.b", 5); long collectionTimeInPast = new DefaultClockImpl().now().getMillis() - 1000 - Configuration.getInstance().getLongProperty( CoreConfig.BEFORE_CURRENT_COLLECTIONTIME_MS ); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, collectionTimeInPast, 0 , new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "Out of bounds. Cannot be more than 259200000 milliseconds into the past. " + "Cannot be more than 600000 milliseconds into the future", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "timestamp", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", "", errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testCollectionTimeInFuture() throws IOException { BluefloodGauge gauge = new BluefloodGauge("gauge.a.b", 5); long collectionTimeInFuture = new DefaultClockImpl().now().getMillis() + 1000 + Configuration.getInstance().getLongProperty( CoreConfig.AFTER_CURRENT_COLLECTIONTIME_MS ); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, collectionTimeInFuture, 0 , new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "Out of bounds. Cannot be more than 259200000 milliseconds into the past. " + "Cannot be more than 600000 milliseconds into the future", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "timestamp", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", "", errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testAggregatedMetricsNotSet() throws IOException { FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0 , null, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "At least one of the aggregated metrics(gauges, counters, timers, sets) " + "are expected", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", "", errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testGaugeEmptyMetricName() throws IOException { BluefloodGauge gauge = new BluefloodGauge("", 5); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be empty", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "gauges[0].name", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testGaugeEmptyMetricValue() throws IOException { String metricName = "gauge.a.b"; BluefloodGauge gauge = new BluefloodGauge(metricName, null); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be null", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "gauges[0].value", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", metricName, errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testCounterEmptyMetricName() throws IOException { BluefloodCounter counter = new BluefloodCounter("", 5, 0.1); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, new BluefloodCounter[]{counter}, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be empty", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "counters[0].name", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testCounterEmptyMetricValue() throws IOException { String metricName = "counter.a.b"; BluefloodCounter counter = new BluefloodCounter(metricName, null, 0.1); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, new BluefloodCounter[]{counter}, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be null", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "counters[0].value", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", metricName, errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testCounterEmptyMetricRate() throws IOException { String metricName = "counter.a.b"; BluefloodCounter counter = new BluefloodCounter(metricName, 5, null); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, new BluefloodCounter[]{counter}, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be null", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "counters[0].rate", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", metricName, errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testTimerEmptyMetricName() throws IOException { BluefloodTimer timer = new BluefloodTimer("", 5); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, null, new BluefloodTimer[]{timer}, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be empty", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "timers[0].name", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testTimerEmptyMetricCount() throws IOException { String metricName = "timer.a.b"; BluefloodTimer timer = new BluefloodTimer(metricName, null); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, null, new BluefloodTimer[]{timer}, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be null", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "timers[0].count", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid metric name", metricName, errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testSetEmptyMetricName() throws IOException { BluefloodSet sets = new BluefloodSet("", new String[]{}); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, null, null, new BluefloodSet[]{sets})); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String errorResponseBody = argument.getValue().content().toString(Charset.defaultCharset()); ErrorResponse errorResponse = getErrorResponse(errorResponseBody); assertEquals("Number of errors invalid", 1, errorResponse.getErrors().size()); assertEquals("Invalid error message", "may not be empty", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid source", "sets[0].name", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid tenant", TENANT, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); } @Test public void testValidGauge() throws IOException { BluefloodGauge gauge = new BluefloodGauge("gauge.a.b", 5); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, new BluefloodGauge[]{gauge}, null, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String responseBody = argument.getValue().content().toString(Charset.defaultCharset()); assertEquals("Invalid response", "", responseBody); assertEquals("Invalid status", HttpResponseStatus.OK, argument.getValue().getStatus()); } @Test public void testValidCounter() throws IOException { BluefloodCounter counter = new BluefloodCounter("counter.a.b", 5, 0.1); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, new BluefloodCounter[]{counter}, null, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String responseBody = argument.getValue().content().toString(Charset.defaultCharset()); assertEquals("Invalid response", "", responseBody); assertEquals("Invalid status", HttpResponseStatus.OK, argument.getValue().getStatus()); } @Test public void testValidTimer() throws IOException { BluefloodTimer timer = new BluefloodTimer("timer.a.b", 5); FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, null, new BluefloodTimer[]{timer}, null)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String responseBody = argument.getValue().content().toString(Charset.defaultCharset()); assertEquals("Invalid response", "", responseBody); assertEquals("Invalid status", HttpResponseStatus.OK, argument.getValue().getStatus()); } @Test public void testValidSet() throws IOException { BluefloodSet set = new BluefloodSet("set.a.b", new String[]{"", ""});; FullHttpRequest request = createIngestRequest(createRequestBody(TENANT, new DefaultClockImpl().now().getMillis(), 0, null, null, null, new BluefloodSet[]{set})); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); String responseBody = argument.getValue().content().toString(Charset.defaultCharset()); assertEquals("Invalid response", "", responseBody); assertEquals("Invalid status", HttpResponseStatus.OK, argument.getValue().getStatus()); } @Test public void perTenantMetricsOn_emptyRequest_shouldNotRecordAnything() throws IOException { String requestBody = "{}"; FullHttpRequest request = createIngestRequest(requestBody); long ingestedMetricsBefore = ingestedMetrics.getCount(); long ingestedDelayedMetricsBefore = ingestedDelayedMetrics.getCount(); HttpAggregatedIngestionHandler handler = spy(new HttpAggregatedIngestionHandler(processor, new TimeValue(5, TimeUnit.SECONDS), true)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); verify(handler, never()).recordPerTenantMetrics(eq(TENANT), anyInt(), anyInt()); assertEquals("ingested metrics count", 0, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 0, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } @Test public void perTenantMetricsOn_shouldRecordDelayedMetrics() throws Exception { BluefloodSet set1 = new BluefloodSet("delayed.me.1.set.a.b", new String[]{"", ""}); BluefloodSet set2 = new BluefloodSet("delayed.me.2.set.a.b", new String[]{"", ""}); long delayedTime = new DefaultClockImpl().now().getMillis() - 100 - Configuration.getInstance().getLongProperty(CoreConfig.ROLLUP_DELAY_MILLIS); FullHttpRequest request = createIngestRequest( createRequestBody(TENANT, delayedTime, 0, null, null, null, new BluefloodSet[]{set1, set2})); long ingestedMetricsBefore = ingestedMetrics.getCount(); long ingestedDelayedMetricsBefore = ingestedDelayedMetrics.getCount(); ListenableFuture<List<Boolean>> futures = mock(ListenableFuture.class); List<Boolean> answers = new ArrayList<>(); answers.add(Boolean.TRUE); when(processor.apply(any())).thenReturn(futures); when(futures.get(anyLong(), any())).thenReturn(answers); HttpAggregatedIngestionHandler handler = spy(new HttpAggregatedIngestionHandler(processor, new TimeValue(5, TimeUnit.SECONDS), true)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); verify(handler, times(1)).recordPerTenantMetrics(eq(TENANT), eq(0), eq(2)); assertEquals("ingested metrics count", 0, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 2, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } @Test public void perTenantMetricsOn_shouldRecordNonDelayedMetrics() throws Exception { BluefloodSet set1 = new BluefloodSet("i.am.on.time.1.set.a.b", new String[]{"", ""}); BluefloodSet set2 = new BluefloodSet("i.am.on.time.2.set.a.b", new String[]{"", ""}); long timestamp = new DefaultClockImpl().now().getMillis(); FullHttpRequest request = createIngestRequest( createRequestBody(TENANT, timestamp, 0, null, null, null, new BluefloodSet[]{set1, set2})); long ingestedMetricsBefore = ingestedMetrics.getCount(); long ingestedDelayedMetricsBefore = ingestedDelayedMetrics.getCount(); ListenableFuture<List<Boolean>> futures = mock(ListenableFuture.class); List<Boolean> answers = new ArrayList<>(); answers.add(Boolean.TRUE); when(processor.apply(any())).thenReturn(futures); when(futures.get(anyLong(), any())).thenReturn(answers); HttpAggregatedIngestionHandler handler = spy(new HttpAggregatedIngestionHandler(processor, new TimeValue(5, TimeUnit.SECONDS), true)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); verify(handler, times(1)).recordPerTenantMetrics(eq(TENANT), eq(2), eq(0)); assertEquals("ingested metrics count", 2, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 0, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } @Test public void perTenantMetricsOff_shouldNotRecordMetrics() throws Exception { BluefloodSet set1 = new BluefloodSet("i.am.on.time.1.set.a.b", new String[]{"", ""}); BluefloodSet set2 = new BluefloodSet("i.am.on.time.2.set.a.b", new String[]{"", ""}); long timestamp = new DefaultClockImpl().now().getMillis(); FullHttpRequest request = createIngestRequest( createRequestBody(TENANT, timestamp, 0, null, null, null, new BluefloodSet[]{set1, set2})); long ingestedMetricsBefore = ingestedMetrics.getCount(); long ingestedDelayedMetricsBefore = ingestedDelayedMetrics.getCount(); ListenableFuture<List<Boolean>> futures = mock(ListenableFuture.class); List<Boolean> answers = new ArrayList<>(); answers.add(Boolean.TRUE); when(processor.apply(any())).thenReturn(futures); when(futures.get(anyLong(), any())).thenReturn(answers); // turn off per tenant metrics tracking HttpAggregatedIngestionHandler handler = spy(new HttpAggregatedIngestionHandler(processor, new TimeValue(5, TimeUnit.SECONDS), false)); ArgumentCaptor<FullHttpResponse> argument = ArgumentCaptor.forClass(FullHttpResponse.class); handler.handle(context, request); verify(channel).write(argument.capture()); verify(handler, times(1)).recordPerTenantMetrics(eq(TENANT), eq(2), eq(0)); assertEquals("ingested metrics count", 0, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 0, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } private String createRequestBody(String tenantId, long collectionTime, long flushInterval, BluefloodGauge[] gauges, BluefloodCounter[] counters, BluefloodTimer[] timers, BluefloodSet[] sets) { AggregatedPayload payload = new AggregatedPayload(tenantId, collectionTime, flushInterval, gauges, counters, timers, sets); return new Gson().toJson(payload, AggregatedPayload.class); } private FullHttpRequest createIngestRequest(String requestBody) { return super.createPostRequest("/v2.0/" + TENANT + "/aggregated/", requestBody); } }