/* * 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.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 junit.framework.Assert; 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.HashSet; import java.util.List; import java.util.concurrent.TimeUnit; import static com.rackspacecloud.blueflood.TestUtils.*; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.*; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; public class HttpAggregatedMultiIngestionHandlerTest extends HandlerTestsBase { private HttpAggregatedMultiIngestionHandler handler; private HttpMetricsIngestionServer.Processor processor; private ChannelHandlerContext context; private Channel channel; private ChannelFuture channelFuture; private Meter ingestedMetrics; private Meter ingestedDelayedMetrics; private static final String TENANT = "tenant"; private List<AggregatedPayload> bundleList; private final String postfix = ".pref"; @Before public void buildBundle() throws Exception { processor = mock(HttpMetricsIngestionServer.Processor.class); handler = new HttpAggregatedMultiIngestionHandler(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>()); String json = getJsonFromFile("sample_multi_aggregated_payload.json", postfix); bundleList = HttpAggregatedMultiIngestionHandler.createBundleList(json); ingestedMetrics = Instrumentation.getIngestedMetricsMeter(TENANT); ingestedDelayedMetrics = Instrumentation.getIngestedDelayedMetricsMeter(TENANT); } @Test public void testMultiBundle() { HashSet<String> tenantIdSet = new HashSet<String>(); HashSet<Long> timestampSet = new HashSet<Long>(); Assert.assertTrue(bundleList.size() == 3); for (AggregatedPayload bundle : bundleList) { tenantIdSet.add(bundle.getTenantId()); timestampSet.add(bundle.getTimestamp()); } Assert.assertTrue(tenantIdSet.size() == 3); //3 unique timestamps are supported Assert.assertTrue(timestampSet.size() == 3); //3 unique tenants are supported } @Test public void testCounters() { for (AggregatedPayload bundle : bundleList) { Collection<PreaggregatedMetric> counters = PreaggregateConversions.convertCounters("1", 1, 15000, bundle.getCounters()); Assert.assertEquals(1, counters.size()); ensureSerializability(counters); } } @Test public void testEmptyButValidMultiJSON() { String badJson = "[]"; List<AggregatedPayload> bundle = HttpAggregatedMultiIngestionHandler.createBundleList(badJson); } @Test public void testGauges() { for (AggregatedPayload bundle : bundleList) { Collection<PreaggregatedMetric> gauges = PreaggregateConversions.convertGauges("1", 1, bundle.getGauges()); Assert.assertEquals(1, gauges.size()); ensureSerializability(gauges); } } @Test public void testSets() { for (AggregatedPayload bundle : bundleList) { Collection<PreaggregatedMetric> sets = PreaggregateConversions.convertSets("1", 1, bundle.getSets()); Assert.assertEquals(1, sets.size()); ensureSerializability(sets); } } @Test public void testTimers() { for (AggregatedPayload bundle : bundleList) { Collection<PreaggregatedMetric> timers = PreaggregateConversions.convertTimers("1", 1, bundle.getTimers()); Assert.assertEquals(1, 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 testNonArrayJsonRequest() 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", 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 testEmptyArrayJsonRequest() 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 responseBody = argument.getValue().content().toString(Charset.defaultCharset()); assertEquals("Invalid response", "No valid metrics", responseBody); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, 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(); HttpAggregatedMultiIngestionHandler handler = spy(new HttpAggregatedMultiIngestionHandler(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 { long delayedTime = new DefaultClockImpl().now().getMillis() - 100 - Configuration.getInstance().getLongProperty(CoreConfig.ROLLUP_DELAY_MILLIS); FullHttpRequest request = createIngestRequest( getJsonFromFile("sample_multi_aggregated_payload.json", delayedTime, postfix)); 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); HttpAggregatedMultiIngestionHandler handler = spy(new HttpAggregatedMultiIngestionHandler(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(12)); assertEquals("ingested metrics count", 0, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 12, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } @Test public void perTenantMetricsOn_shouldRecordNonDelayedMetrics() throws Exception { long timestamp = new DefaultClockImpl().now().getMillis(); FullHttpRequest request = createIngestRequest( getJsonFromFile("sample_multi_aggregated_payload.json", timestamp, postfix)); 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); HttpAggregatedMultiIngestionHandler handler = spy(new HttpAggregatedMultiIngestionHandler(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(12), eq(0)); assertEquals("ingested metrics count", 12, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 0, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } @Test public void perTenantMetricsOff_shouldNotRecordMetrics() throws Exception { long timestamp = new DefaultClockImpl().now().getMillis(); FullHttpRequest request = createIngestRequest( getJsonFromFile("sample_multi_aggregated_payload.json", timestamp, postfix)); 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 HttpAggregatedMultiIngestionHandler handler = spy(new HttpAggregatedMultiIngestionHandler(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(12), eq(0)); assertEquals("ingested metrics count", 0, ingestedMetrics.getCount() - ingestedMetricsBefore); assertEquals("ingested delayed metrics count", 0, ingestedDelayedMetrics.getCount() - ingestedDelayedMetricsBefore); } private FullHttpRequest createIngestRequest(String requestBody) { return super.createPostRequest("/v2.0/" + TENANT + "/aggregated/multi", requestBody); } }