package com.rackspacecloud.blueflood.tracker;
import com.rackspacecloud.blueflood.http.HttpRequestWithDecodedQueryParams;
import com.rackspacecloud.blueflood.types.Locator;
import com.rackspacecloud.blueflood.types.Metric;
import com.rackspacecloud.blueflood.utils.TimeValue;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpHeaders;
import junit.framework.Assert;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
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 static org.hamcrest.CoreMatchers.not;
import static org.junit.matchers.JUnitMatchers.containsString;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.mock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
@PowerMockIgnore( {"javax.management.*"})
public class TrackerTest {
Tracker tracker = Tracker.getInstance();
String testTenant1 = "tenant1";
String testTenant2 = "tenant2";
String tenantId = "121212";
private static Logger loggerMock;
private HttpRequestWithDecodedQueryParams httpRequestMock;
private HttpMethod httpMethodMock;
private FullHttpResponse httpResponseMock;
private HttpResponseStatus httpResponseStatusMock;
private Map<String, List<String>> queryParams;
private List<Metric> delayedMetrics;
private long collectionTime;
@Captor
ArgumentCaptor argCaptor;
@BeforeClass
public static void setupClass() {
// mock logger
PowerMockito.mockStatic(LoggerFactory.class);
loggerMock = mock(Logger.class);
when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);
}
@Before
public void setUp() {
// reset static logger mock for every new test setup
reset(loggerMock);
// mock HttpRequest, method, queryParams, channelBuffer, responseStatus
httpRequestMock = mock(HttpRequestWithDecodedQueryParams.class);
httpMethodMock = mock(HttpMethod.class);
queryParams = new HashMap<String, List<String>>();
// mock HttpResponse and HttpResponseStatus
httpResponseMock = mock(FullHttpResponse.class);
httpResponseStatusMock = mock(HttpResponseStatus.class);
// mock headers
HttpHeaders headers = mock(HttpHeaders.class);
Set<String> headerNames = new HashSet<String>();
headerNames.add("X-Auth-Token");
when(httpRequestMock.headers()).thenReturn(headers);
when(headers.names()).thenReturn(headerNames);
when(headers.get("X-Auth-Token")).thenReturn("AUTHTOKEN");
HttpHeaders responseHeaders = mock(HttpHeaders.class);
when(httpResponseMock.headers()).thenReturn(responseHeaders);
when(responseHeaders.names()).thenReturn(new HashSet<String>());
// setup delayed metrics
Locator locator1 = Locator.createLocatorFromPathComponents(tenantId, "delayed", "metric1");
Locator locator2 = Locator.createLocatorFromPathComponents(tenantId, "delayed", "metric2");
collectionTime = 1451606400000L; // Jan 1, 2016 UTC
TimeValue ttl = new TimeValue(3, TimeUnit.DAYS);
Metric delayMetric1 = new Metric(locator1, 123, collectionTime, ttl, "");
Metric delayMetric2 = new Metric(locator2, 456, collectionTime, ttl, "");
delayedMetrics = new ArrayList<Metric>();
delayedMetrics.add(delayMetric1);
delayedMetrics.add(delayMetric2);
}
@Test
public void testRegister() {
// register the tracker and verify
tracker.register();
verify(loggerMock, times(1)).info("MBean registered as com.rackspacecloud.blueflood.tracker:type=Tracker");
}
@Test
public void testAddTenant() {
tracker.addTenant(testTenant1);
Set tenants = tracker.getTenants();
assertTrue( "tenant " + testTenant1 + " not added", tracker.isTracking( testTenant1 ) );
assertTrue( "tenants.size not 1", tenants.size() == 1 );
assertTrue( "tenants does not contain " + testTenant1, tenants.contains( testTenant1 ) );
}
@Test
public void testDoesNotAddTenantTwice() {
tracker.addTenant(testTenant1);
tracker.addTenant(testTenant1);
Set tenants = tracker.getTenants();
assertTrue( "tenant " + testTenant1 + " not added", tracker.isTracking( testTenant1 ) );
assertTrue( "tenants.size not 1", tenants.size() == 1 );
}
@Test
public void testRemoveTenant() {
tracker.addTenant(testTenant1);
assertTrue( "tenant " + testTenant1 + " not added", tracker.isTracking( testTenant1 ) );
tracker.removeTenant(testTenant1);
Set tenants = tracker.getTenants();
assertFalse( "tenant " + testTenant1 + " not removed", tracker.isTracking( testTenant1 ) );
assertEquals( "tenants.size not 0", tenants.size(), 0 );
assertFalse( "tenants contains " + testTenant1, tenants.contains( testTenant1 ) );
}
@Test
public void testAddAndRemoveMetricName() {
String metric1 = "metricName";
String metric2 = "anotherMetricName";
tracker.addMetricName(metric1);
assertTrue(metric1 + " not added", tracker.doesMessageContainMetricNames("Track.this." + metric1));
tracker.addMetricName(metric2);
assertTrue(metric1 + " not being logged", tracker.doesMessageContainMetricNames("Track.this." + metric1));
assertTrue(metric2 + "not being logged", tracker.doesMessageContainMetricNames("Track.this." + metric2));
assertFalse("randomMetricNameNom should not be logged", tracker.doesMessageContainMetricNames("Track.this.randomMetricNameNom"));
Set<String> metricNames = tracker.getMetricNames();
assertTrue("metricNames should contain " + metric1, metricNames.contains(metric1));
assertTrue("metricNames should contain " + metric2, metricNames.contains(metric2));
tracker.removeMetricName(metric1);
assertFalse(metric1 + " should not be logged", tracker.doesMessageContainMetricNames("Track.this." + metric1));
metricNames = tracker.getMetricNames();
assertFalse("metricNames should not contain " + metric1, metricNames.contains(metric1));
assertTrue("metricNames should contain " + metric2, metricNames.contains(metric2));
}
@Test
public void testRemoveAllMetricNames() {
tracker.addMetricName("metricName");
tracker.addMetricName("anotherMetricNom");
assertTrue("metricName not being logged",tracker.doesMessageContainMetricNames("Track.this.metricName"));
assertTrue("anotherMetricNom not being logged",tracker.doesMessageContainMetricNames("Track.this.anotherMetricNom"));
assertFalse("randomMetricNameNom should not be logged", tracker.doesMessageContainMetricNames("Track.this.randomMetricNameNom"));
tracker.removeAllMetricNames();
assertTrue("Everything should be logged", tracker.doesMessageContainMetricNames("Track.this.metricName"));
assertTrue("Everything should be logged", tracker.doesMessageContainMetricNames("Track.this.anotherMetricNom"));
assertTrue("Everything should be logged", tracker.doesMessageContainMetricNames("Track.this.randomMetricNameNom"));
}
@Test
public void testRemoveAllTenant() {
tracker.addTenant(testTenant1);
tracker.addTenant(testTenant2);
assertTrue( "tenant " + testTenant1 + " not added", tracker.isTracking( testTenant1 ) );
assertTrue( "tenant " + testTenant2 + " not added", tracker.isTracking( testTenant2 ) );
tracker.removeAllTenants();
assertFalse( "tenant " + testTenant1 + " not removed", tracker.isTracking( testTenant1 ) );
assertFalse( "tenant " + testTenant2 + " not removed", tracker.isTracking( testTenant2 ) );
Set tenants = tracker.getTenants();
assertEquals( "tenants.size not 0", tenants.size(), 0 );
}
@Test
public void testFindTidFound() {
assertEquals( tracker.findTid( "/v2.0/6000/views" ), "6000" );
}
@Test
public void testTrackTenantNoVersion() {
assertEquals( tracker.findTid( "/6000/views" ), null );
}
@Test
public void testTrackTenantBadVersion() {
assertEquals( tracker.findTid( "blah/6000/views" ), null );
}
@Test
public void testTrackTenantTrailingSlash() {
assertEquals( tracker.findTid( "/v2.0/6000/views/" ), "6000" );
}
@Test
public void testSetIsTrackingDelayedMetrics() {
tracker.resetIsTrackingDelayedMetrics();
tracker.setIsTrackingDelayedMetrics();
Assert.assertTrue("isTrackingDelayedMetrics should be true from setIsTrackingDelayedMetrics", tracker.getIsTrackingDelayedMetrics());
}
@Test
public void testResetIsTrackingDelayedMetricss() {
tracker.setIsTrackingDelayedMetrics();
tracker.resetIsTrackingDelayedMetrics();
Assert.assertFalse("isTrackingDelayedMetrics should be false from resetIsTrackingDelayedMetrics", tracker.getIsTrackingDelayedMetrics());
}
@Test
public void testTrackTenant() throws Exception {
// setup mock returns for ingest POST
when(httpRequestMock.getUri()).thenReturn("/v2.0/" + tenantId + "/ingest");
when(httpMethodMock.toString()).thenReturn("POST");
when(httpRequestMock.getMethod()).thenReturn(httpMethodMock);
List<String> paramValues = new ArrayList<String>();
paramValues.add("value1");
paramValues.add("value2");
queryParams.put("param1", paramValues);
when((httpRequestMock).getQueryParams()).thenReturn(queryParams);
String payload = "[\n" +
" {\n" +
" \"collectionTime\": 1376509892612,\n" +
" \"ttlInSeconds\": 172800,\n" +
" \"metricValue\": 65,\n" +
" \"metricName\": \"example.metric.one\"\n" +
" },\n" +
" {\n" +
" \"collectionTime\": 1376509892612,\n" +
" \"ttlInSeconds\": 172800,\n" +
" \"metricValue\": 66,\n" +
" \"metricName\": \"example.metric.two\"\n" +
" },\n" +
" ]'";
when(httpRequestMock.content()).thenReturn(Unpooled.copiedBuffer(payload.getBytes("UTF-8")));
// add tenant and track
tracker.addTenant(tenantId);
tracker.track(httpRequestMock);
// verify
verify(loggerMock, atLeastOnce()).info("[TRACKER] tenantId " + tenantId + " added.");
verify(loggerMock, atLeastOnce()).info((String)argCaptor.capture());
assertThat(argCaptor.getValue().toString(), containsString("[TRACKER] POST request for tenantId " + tenantId + ": /v2.0/" + tenantId + "/ingest?param1=value1¶m1=value2\n" +
"HEADERS: \n" +
"X-Auth-Token\tAUTHTOKEN\n" +
"REQUEST_CONTENT:\n" +
"[\n" +
" {\n" +
" \"collectionTime\": 1376509892612,\n" +
" \"ttlInSeconds\": 172800,\n" +
" \"metricValue\": 65,\n" +
" \"metricName\": \"example.metric.one\"\n" +
" },\n" +
" {\n" +
" \"collectionTime\": 1376509892612,\n" +
" \"ttlInSeconds\": 172800,\n" +
" \"metricValue\": 66,\n" +
" \"metricName\": \"example.metric.two\"\n" +
" },\n" +
" ]'"));
}
@Test
public void testTrackNullRequest() {
when(httpRequestMock.getUri()).thenReturn("/v2.0/" + tenantId + "/ingest");
// add tenant to track but provide a null request for tracking
tracker.addTenant(tenantId);
tracker.track(null);
// verify logger does not get called for tracking request
verify(httpRequestMock, never()).getUri();
verify(loggerMock, atLeastOnce()).info((String)argCaptor.capture());
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] POST request for tenantId " + tenantId)));
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] GET request for tenantId " + tenantId)));
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] PUT request for tenantId " + tenantId)));
}
@Test
public void testDoesNotTrackTenant() {
// setup mock returns URI
when(httpRequestMock.getUri()).thenReturn("/v2.0/" + tenantId + "/ingest");
// make sure tenantId is removed and track
tracker.removeTenant(tenantId);
tracker.track(httpRequestMock);
// verify does not log for tenantId
verify(loggerMock, atLeastOnce()).info((String)argCaptor.capture());
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] POST request for tenantId " + tenantId)));
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] GET request for tenantId " + tenantId)));
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] PUT request for tenantId " + tenantId)));
}
@Test
public void testTrackResponse() throws Exception {
// setup mock returns for query POST
String requestUri = "/v2.0/" + tenantId + "/metrics/search";
when(httpRequestMock.getUri()).thenReturn(requestUri);
when(httpMethodMock.toString()).thenReturn("GET");
when(httpRequestMock.getMethod()).thenReturn(httpMethodMock);
List<String> paramValues = new ArrayList<String>();
paramValues.add("locator1");
queryParams.put("query", paramValues);
when((httpRequestMock).getQueryParams()).thenReturn(queryParams);
when(httpResponseStatusMock.code()).thenReturn(200);
when(httpResponseMock.getStatus()).thenReturn(httpResponseStatusMock);
//when(channelBufferMock.toString(any(Charset.class))).thenReturn(
String result = "[TRACKER] Response for tenantId " + tenantId;
when(httpResponseMock.content()).thenReturn(Unpooled.copiedBuffer(result.getBytes("UTF-8")));
// add tenant and track
tracker.addTenant(tenantId);
tracker.trackResponse(httpRequestMock, httpResponseMock);
// verify
verify(loggerMock, atLeastOnce()).info("[TRACKER] tenantId " + tenantId + " added.");
verify(loggerMock, times(1)).info("[TRACKER] Response for tenantId " + tenantId + " GET request " + requestUri + "?query=locator1\n" +
"RESPONSE_STATUS: 200\n" +
"RESPONSE HEADERS: \n" +
"RESPONSE_CONTENT:\n" +
"[TRACKER] Response for tenantId " + tenantId);
}
@Test
public void testTrackNullResponse() {
String requestUri = "/v2.0/" + tenantId + "/metrics/search";
when(httpRequestMock.getUri()).thenReturn(requestUri);
// add tenant to track but provide a null response for tracking
tracker.addTenant(tenantId);
tracker.trackResponse(httpRequestMock, null);
// verify logger does not get called for tracking response
verify(httpRequestMock, never()).getUri();
verify(loggerMock, atLeastOnce()).info((String)argCaptor.capture());
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] Response for tenantId " + tenantId)));
}
@Test
public void testDoesNotTrackTenantResponse() {
// setup mock returns URI
when(httpRequestMock.getUri()).thenReturn("/v2.0/" + tenantId + "/metrics/search");
// make sure tenantId is removed and track
tracker.removeTenant(tenantId);
tracker.trackResponse(httpRequestMock, httpResponseMock);
// verify does not log for tenantId
verify(loggerMock, atLeastOnce()).info((String)argCaptor.capture());
assertThat(argCaptor.getValue().toString(), not(containsString("[TRACKER] Response for tenantId " + tenantId)));
}
@Test
public void testTrackDelayedMetricsTenant() {
// enable tracking delayed metrics and track
tracker.setIsTrackingDelayedMetrics();
tracker.trackDelayedMetricsTenant(tenantId, delayedMetrics);
// verify
verify(loggerMock, atLeastOnce()).info("[TRACKER] Tracking delayed metrics started");
verify(loggerMock, atLeastOnce()).info("[TRACKER][DELAYED METRIC] Tenant sending delayed metrics " + tenantId);
verify(loggerMock, atLeastOnce()).info(contains("[TRACKER][DELAYED METRIC] " + tenantId + ".delayed.metric1 has collectionTime 2016-01-01 00:00:00 which is delayed"));
verify(loggerMock, atLeastOnce()).info(contains("[TRACKER][DELAYED METRIC] " + tenantId + ".delayed.metric2 has collectionTime 2016-01-01 00:00:00 which is delayed"));
}
@Test
public void testDoesNotTrackDelayedMetricsTenant() {
// disable tracking delayed metrics and track
tracker.resetIsTrackingDelayedMetrics();
tracker.trackDelayedMetricsTenant(tenantId, delayedMetrics);
// verify
verify(loggerMock, atLeastOnce()).info("[TRACKER] Tracking delayed metrics stopped");
verify(loggerMock, never()).info("[TRACKER][DELAYED METRIC] Tenant sending delayed metrics " + tenantId);
verify(loggerMock, never()).info(contains("[TRACKER][DELAYED METRIC] " + tenantId + ".delayed.metric1 has collectionTime 2016-01-01 00:00:00"));
verify(loggerMock, never()).info(contains("[TRACKER][DELAYED METRIC] " + tenantId + ".delayed.metric2 has collectionTime 2016-01-01 00:00:00"));
}
@Test
public void testTrackDelayedAggregatedMetricsTenant() {
// enable tracking delayed metrics and track
tracker.setIsTrackingDelayedMetrics();
List<String> delayedMetricNames = new ArrayList<String>() {{
for ( Metric metric : delayedMetrics ) {
add(metric.getLocator().toString());
}
}};
long ingestTime = System.currentTimeMillis();
tracker.trackDelayedAggregatedMetricsTenant(tenantId, delayedMetrics.get(0).getCollectionTime(), ingestTime, delayedMetricNames);
// verify
verify(loggerMock, atLeastOnce()).info("[TRACKER] Tracking delayed metrics started");
verify(loggerMock, atLeastOnce()).info("[TRACKER][DELAYED METRIC] Tenant sending delayed metrics " + tenantId);
verify(loggerMock, atLeastOnce()).info(contains("[TRACKER][DELAYED METRIC] " + tenantId + ".delayed.metric1" + "," +
tenantId + ".delayed.metric2 have collectionTime 2016-01-01 00:00:00 which is delayed"));
}
@Test
public void testDoesNotTrackDelayedAggregatedMetricsTenant() {
// disable tracking delayed metrics and track
tracker.resetIsTrackingDelayedMetrics();
tracker.trackDelayedMetricsTenant(tenantId, delayedMetrics);
// verify
verify(loggerMock, atLeastOnce()).info("[TRACKER] Tracking delayed metrics stopped");
verify(loggerMock, never()).info("[TRACKER][DELAYED METRIC] Tenant sending delayed metrics " + tenantId);
verify(loggerMock, never()).info(contains("[TRACKER][DELAYED METRIC] " + tenantId + ".delayed.metric1" + "," +
tenantId + ".delayed.metric2 have collectionTime 2016-01-01 00:00:00 which is delayed"));
}
}