package com.rackspacecloud.blueflood.inputs.handlers; import com.rackspacecloud.blueflood.inputs.formats.JSONMetricScoped; 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.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.*; import org.codehaus.jackson.map.ObjectMapper; import org.elasticsearch.common.lang3.StringUtils; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; public class HttpMultitenantMetricsIngestionHandlerTest extends HandlerTestsBase { private HttpMultitenantMetricsIngestionHandler handler; private HttpMetricsIngestionServer.Processor processor; private ChannelHandlerContext context; private Channel channel; private ChannelFuture channelFuture; private static final String TENANT = "tenant"; @Before public void setup() { processor = mock(HttpMetricsIngestionServer.Processor.class); handler = new HttpMultitenantMetricsIngestionHandler(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); } @Test public void testMultiMetricsEmptyArrays() throws IOException { String requestBody = "[]"; FullHttpRequest request = createRequest(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 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()); assertEquals("Invalid error source", null, errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid error message", "No valid metrics", errorResponse.getErrors().get(0).getMessage()); } @Test public void testSingleMetricEmptyTenantId() throws IOException { String metricName = "a.b.c"; String singleMetric = createRequestBody("", metricName, new DefaultClockImpl().now().getMillis(), 24 * 60 * 60, 1); //empty metric name String requestBody = "[" + singleMetric + "]"; FullHttpRequest request = createRequest(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 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()); assertEquals("Invalid error source", "tenantId", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid error message", "may not be empty", errorResponse.getErrors().get(0).getMessage()); } @Test public void testMultiMetricsInvalidRequest() throws IOException { final String TENANT1 = "tenant1"; final String TENANT2 = "tenant2"; final String metricName1 = "a.b.c.1"; final String metricName2 = "a.b.c.2"; long collectionTimeInPast = new DefaultClockImpl().now().getMillis() - 1000 - Configuration.getInstance().getLongProperty( CoreConfig.BEFORE_CURRENT_COLLECTIONTIME_MS ); String singleMetric1 = createRequestBody(TENANT1, metricName1, new DefaultClockImpl().now().getMillis(), -1, 1); //invalid ttl String singleMetric2 = createRequestBody(TENANT2, metricName2, collectionTimeInPast, 24 * 60 * 60, 1); //collection in past String requestBody = "[" + singleMetric1 + "," + singleMetric2 + "]"; FullHttpRequest request = createRequest(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", 2, errorResponse.getErrors().size()); assertEquals("Invalid status", HttpResponseStatus.BAD_REQUEST, argument.getValue().getStatus()); assertEquals("Invalid tenant", TENANT1, errorResponse.getErrors().get(0).getTenantId()); assertEquals("Invalid tenant", metricName1, errorResponse.getErrors().get(0).getMetricName()); assertEquals("Invalid error source", "ttlInSeconds", errorResponse.getErrors().get(0).getSource()); assertEquals("Invalid error message", "must be between 1 and 2147483647", errorResponse.getErrors().get(0).getMessage()); assertEquals("Invalid tenant", TENANT2, errorResponse.getErrors().get(1).getTenantId()); assertEquals("Invalid tenant", metricName2, errorResponse.getErrors().get(1).getMetricName()); assertEquals("Invalid error source", "collectionTime", errorResponse.getErrors().get(1).getSource()); 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(1).getMessage()); } private String createRequestBody(String tenantId, String metricName, long collectionTime, int ttl, Object metricValue) throws IOException { JSONMetricScoped metric = new JSONMetricScoped(); if (!StringUtils.isEmpty(tenantId)) metric.setTenantId(tenantId); if (!StringUtils.isEmpty(metricName)) metric.setMetricName(metricName); if (collectionTime > 0) metric.setCollectionTime(collectionTime); if (ttl > 0) metric.setTtlInSeconds(ttl); if (metricValue != null) metric.setMetricValue(metricValue); return new ObjectMapper().writeValueAsString(metric); } private FullHttpRequest createRequest(String requestBody) { return super.createPostRequest("/v2.0/" + TENANT + "/ingest/multi", requestBody); } }