blob: d8fcde2cab1639cca622d8785df75d95743ac6f6 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.flume.sink.http;
import org.apache.flume.Channel;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.Sink.Status;
import org.apache.flume.Transaction;
import org.apache.flume.instrumentation.SinkCounter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TestHttpSink {
private static final Integer DEFAULT_REQUEST_TIMEOUT = 5000;
private static final Integer DEFAULT_CONNECT_TIMEOUT = 5000;
private static final String DEFAULT_ACCEPT_HEADER = "text/plain";
private static final String DEFAULT_CONTENT_TYPE_HEADER = "text/plain";
@Mock
private SinkCounter sinkCounter;
@Mock
private Context configContext;
@Mock
private Channel channel;
@Mock
private Transaction transaction;
@Mock
private Event event;
@Mock
private HttpURLConnection httpURLConnection;
@Mock
private OutputStream outputStream;
@Mock
private InputStream inputStream;
@Test
public void ensureAllConfigurationOptionsRead() {
whenDefaultStringConfig();
whenDefaultBooleanConfig();
when(configContext.getInteger(eq("connectTimeout"), Mockito.anyInt())).thenReturn(1000);
when(configContext.getInteger(eq("requestTimeout"), Mockito.anyInt())).thenReturn(1000);
new HttpSink().configure(configContext);
verify(configContext).getString("endpoint", "");
verify(configContext).getInteger(eq("connectTimeout"), Mockito.anyInt());
verify(configContext).getInteger(eq("requestTimeout"), Mockito.anyInt());
verify(configContext).getString(eq("acceptHeader"), Mockito.anyString());
verify(configContext).getString(eq("contentTypeHeader"), Mockito.anyString());
verify(configContext).getBoolean("defaultBackoff", true);
verify(configContext).getBoolean("defaultRollback", true);
verify(configContext).getBoolean("defaultIncrementMetrics", false);
}
@Test(expected = IllegalArgumentException.class)
public void ensureExceptionIfEndpointUrlEmpty() {
when(configContext.getString("endpoint", "")).thenReturn("");
new HttpSink().configure(configContext);
}
@Test(expected = IllegalArgumentException.class)
public void ensureExceptionIfEndpointUrlInvalid() {
when(configContext.getString("endpoint", "")).thenReturn("invalid url");
new HttpSink().configure(configContext);
}
@Test(expected = IllegalArgumentException.class)
public void ensureExceptionIfConnectTimeoutNegative() {
whenDefaultStringConfig();
when(configContext.getInteger("connectTimeout", 1000)).thenReturn(-1000);
when(configContext.getInteger(eq("requestTimeout"), Mockito.anyInt())).thenReturn(1000);
new HttpSink().configure(configContext);
}
@Test
public void ensureDefaultConnectTimeoutCorrect() {
whenDefaultStringConfig();
when(configContext.getInteger("connectTimeout", DEFAULT_CONNECT_TIMEOUT)).thenReturn(1000);
when(configContext.getInteger(eq("requestTimeout"), Mockito.anyInt())).thenReturn(1000);
new HttpSink().configure(configContext);
verify(configContext).getInteger("connectTimeout", DEFAULT_CONNECT_TIMEOUT);
}
@Test(expected = IllegalArgumentException.class)
public void ensureExceptionIfRequestTimeoutNegative() {
whenDefaultStringConfig();
when(configContext.getInteger("requestTimeout", 1000)).thenReturn(-1000);
when(configContext.getInteger(eq("connectTimeout"), Mockito.anyInt())).thenReturn(1000);
new HttpSink().configure(configContext);
}
@Test
public void ensureDefaultRequestTimeoutCorrect() {
whenDefaultStringConfig();
when(configContext.getInteger("requestTimeout", DEFAULT_REQUEST_TIMEOUT)).thenReturn(1000);
when(configContext.getInteger(eq("connectTimeout"), Mockito.anyInt())).thenReturn(1000);
new HttpSink().configure(configContext);
verify(configContext).getInteger("requestTimeout", DEFAULT_REQUEST_TIMEOUT);
}
@Test
public void ensureDefaultAcceptHeaderCorrect() {
whenDefaultTimeouts();
whenDefaultStringConfig();
new HttpSink().configure(configContext);
verify(configContext).getString("acceptHeader", DEFAULT_ACCEPT_HEADER);
}
@Test
public void ensureDefaultContentTypeHeaderCorrect() {
whenDefaultTimeouts();
whenDefaultStringConfig();
new HttpSink().configure(configContext);
verify(configContext).getString("contentTypeHeader", DEFAULT_CONTENT_TYPE_HEADER);
}
@Test
public void ensureBackoffOnNullEvent() throws Exception {
when(channel.take()).thenReturn(null);
executeWithMocks(true);
}
@Test
public void ensureBackoffOnNullEventBody() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn(null);
executeWithMocks(true);
}
@Test
public void ensureBackoffOnEmptyEvent() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn(new byte[]{});
executeWithMocks(true);
}
@Test
public void ensureRollbackBackoffAndIncrementMetricsIfConfigured() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn("something".getBytes());
Context context = new Context();
context.put("defaultRollback", "true");
context.put("defaultBackoff", "true");
context.put("defaultIncrementMetrics", "true");
executeWithMocks(false, Status.BACKOFF, true, true, context, HttpURLConnection.HTTP_OK);
}
@Test
public void ensureCommitReadyAndNoIncrementMetricsIfConfigured() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn("something".getBytes());
Context context = new Context();
context.put("defaultRollback", "false");
context.put("defaultBackoff", "false");
context.put("defaultIncrementMetrics", "false");
executeWithMocks(true, Status.READY, false, false, context, HttpURLConnection.HTTP_OK);
}
@Test
public void ensureSingleStatusConfigurationCorrectlyUsed() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn("something".getBytes());
Context context = new Context();
context.put("defaultRollback", "true");
context.put("defaultBackoff", "true");
context.put("defaultIncrementMetrics", "false");
context.put("rollback.200", "false");
context.put("backoff.200", "false");
context.put("incrementMetrics.200", "true");
executeWithMocks(true, Status.READY, true, true, context, HttpURLConnection.HTTP_OK);
}
@Test
public void testErrorCounter() throws Exception {
RuntimeException exception = new RuntimeException("dummy");
when(channel.take()).thenThrow(exception);
Context context = new Context();
context.put("defaultRollback", "false");
context.put("defaultBackoff", "false");
context.put("defaultIncrementMetrics", "false");
executeWithMocks(false, Status.BACKOFF, false, false, context, HttpURLConnection.HTTP_OK);
inOrder(sinkCounter).verify(sinkCounter).incrementEventWriteOrChannelFail(exception);
}
@Test
public void ensureSingleErrorStatusConfigurationCorrectlyUsed() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn("something".getBytes());
Context context = new Context();
context.put("defaultRollback", "true");
context.put("defaultBackoff", "true");
context.put("defaultIncrementMetrics", "false");
context.put("rollback.401", "false");
context.put("backoff.401", "false");
context.put("incrementMetrics.401", "false");
executeWithMocks(true, Status.READY, false, true, context, HttpURLConnection.HTTP_UNAUTHORIZED);
}
@Test
public void ensureGroupConfigurationCorrectlyUsed() throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn("something".getBytes());
Context context = new Context();
context.put("defaultRollback", "true");
context.put("defaultBackoff", "true");
context.put("defaultIncrementMetrics", "false");
context.put("rollback.2XX", "false");
context.put("backoff.2XX", "false");
context.put("incrementMetrics.2XX", "true");
executeWithMocks(true, Status.READY, true, true, context, HttpURLConnection.HTTP_OK);
executeWithMocks(true, Status.READY, true, true, context, HttpURLConnection.HTTP_NO_CONTENT);
}
@Test
public void ensureSingleStatusConfigurationOverridesGroupConfigurationCorrectly()
throws Exception {
when(channel.take()).thenReturn(event);
when(event.getBody()).thenReturn("something".getBytes());
Context context = new Context();
context.put("rollback.2XX", "false");
context.put("backoff.2XX", "false");
context.put("incrementMetrics.2XX", "true");
context.put("rollback.200", "true");
context.put("backoff.200", "true");
context.put("incrementMetrics.200", "false");
executeWithMocks(true, Status.READY, true, true, context, HttpURLConnection.HTTP_NO_CONTENT);
executeWithMocks(false, Status.BACKOFF, false, true, context, HttpURLConnection.HTTP_OK);
}
private void executeWithMocks(boolean commit) throws Exception {
Context context = new Context();
executeWithMocks(commit, Status.BACKOFF, false, false, context, HttpURLConnection.HTTP_OK);
}
private void executeWithMocks(boolean expectedCommit, Status expectedStatus,
boolean expectedIncrementSuccessMetrics,
boolean expectedIncrementAttemptMetrics,
Context context, int httpStatus)
throws Exception {
context.put("endpoint", "http://localhost:8080/endpoint");
HttpSink httpSink = new HttpSink();
httpSink.configure(context);
httpSink.setConnectionBuilder(httpSink.new ConnectionBuilder() {
@Override
public HttpURLConnection getConnection() throws IOException {
return httpURLConnection;
}
});
httpSink.setChannel(channel);
httpSink.setSinkCounter(sinkCounter);
when(channel.getTransaction()).thenReturn(transaction);
when(httpURLConnection.getOutputStream()).thenReturn(outputStream);
when(httpURLConnection.getInputStream()).thenReturn(inputStream);
when(httpURLConnection.getErrorStream()).thenReturn(inputStream);
when(httpURLConnection.getResponseCode()).thenReturn(httpStatus);
Status actualStatus = httpSink.process();
assert (actualStatus == expectedStatus);
inOrder(transaction).verify(transaction).begin();
if (expectedIncrementAttemptMetrics) {
inOrder(sinkCounter).verify(sinkCounter).incrementEventDrainAttemptCount();
}
if (expectedCommit) {
inOrder(transaction).verify(transaction).commit();
} else {
inOrder(transaction).verify(transaction).rollback();
}
if (expectedIncrementSuccessMetrics) {
inOrder(sinkCounter).verify(sinkCounter).incrementEventDrainSuccessCount();
}
inOrder(transaction).verify(transaction).close();
}
private void whenDefaultStringConfig() {
when(configContext.getString("endpoint", "")).thenReturn("http://test.abc/");
when(configContext.getString("acceptHeader", "")).thenReturn("test/accept");
when(configContext.getString("contentTypeHeader", "")).thenReturn("test/content");
}
private void whenDefaultBooleanConfig() {
when(configContext.getBoolean("defaultBackoff", true)).thenReturn(true);
when(configContext.getBoolean("defaultRollback", true)).thenReturn(true);
when(configContext.getBoolean("defaultIncrementMetrics", false)).thenReturn(true);
}
private void whenDefaultTimeouts() {
when(configContext.getInteger(eq("requestTimeout"), Mockito.anyInt())).thenReturn(1000);
when(configContext.getInteger(eq("connectTimeout"), Mockito.anyInt())).thenReturn(1000);
}
}