/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.flume.handlers.endtoend;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Test;
import com.cloudera.flume.conf.Context;
import com.cloudera.flume.conf.FlumeBuilder;
import com.cloudera.flume.conf.FlumeSpecException;
import com.cloudera.flume.core.Event;
import com.cloudera.flume.core.EventImpl;
import com.cloudera.flume.core.EventSink;
import com.cloudera.flume.core.EventSinkDecorator;
import com.cloudera.flume.core.FanOutSink;
import com.cloudera.flume.handlers.debug.ConsoleEventSink;
import com.cloudera.flume.handlers.debug.MemorySinkSource;
import com.cloudera.flume.reporter.aggregator.CounterSink;
/**
* Some tests to verify that the checksums are calculated and are equivalent on
* both sides.
*/
public class TestAckChecksumDecos {
public static final Logger LOG = LoggerFactory.getLogger(TestAckChecksumDecos.class);
// Just a test to see the output.
@Test
public void testCheckCheckerConsole() throws IOException {
int msgs = 5;
CounterSink cnt = new CounterSink("count");
AckChecksumInjector<EventSink> aci =
new AckChecksumInjector<EventSink>(new FanOutSink<EventSink>(cnt,
new ConsoleEventSink()));
aci.open();
for (int i = 0; i < msgs; i++) {
Event e = new EventImpl(("this is a test " + i).getBytes());
aci.append(e);
}
aci.close();
assertEquals(5 + 2, cnt.getCount()); // should have gotten 5 messages + 1
// start ack and 1 stop ack.
}
/**
* Send messages in order and make sure they check out
*/
@Test
public void testCheckChecker() throws IOException {
int msgs = 100;
MemorySinkSource mss = new MemorySinkSource();
AckChecksumChecker<EventSink> acc = new AckChecksumChecker<EventSink>(mss);
AckChecksumInjector<EventSink> aci =
new AckChecksumInjector<EventSink>(acc);
aci.open();
for (int i = 0; i < msgs; i++) {
Event e = new EventImpl(("this is a test " + i).getBytes());
aci.append(e);
}
aci.close(); // will throw exception if checksum doesn't match
Event eo = null;
int count = 0;
while ((eo = mss.next()) != null) {
System.out.println(eo);
count++;
}
assertEquals(msgs, count); // extra ack messages should have been consumed
}
/**
* Send messages when some message are reordered and make sure they check out
*/
@Test
public void testReorderedChecker() throws IOException {
MemorySinkSource mss = new MemorySinkSource();
AckChecksumChecker<EventSink> cc = new AckChecksumChecker<EventSink>(mss);
ReorderDecorator<EventSink> ro =
new ReorderDecorator<EventSink>(cc, .5, .5, 0);
AckChecksumInjector<EventSink> cp = new AckChecksumInjector<EventSink>(ro);
cp.open();
for (int i = 0; i < 100; i++) {
Event e = new EventImpl(("this is a test " + i).getBytes());
cp.append(e);
}
cp.close();
Event eo = null;
while ((eo = mss.next()) != null) {
System.out.println(eo);
}
}
/**
* error case: no start message
*/
@Test
public void testNoStart() throws IOException {
final int msgs = 100;
MemorySinkSource mss = new MemorySinkSource();
AckChecksumChecker<EventSink> cc = new AckChecksumChecker<EventSink>(mss);
EventSinkDecorator<EventSink> dropFirst =
new EventSinkDecorator<EventSink>(cc) {
int count = 0;
public void append(Event e) throws IOException {
if (count == 0) {
count++;
return;
}
count++;
getSink().append(e);
}
};
AckChecksumInjector<EventSink> cp =
new AckChecksumInjector<EventSink>(dropFirst);
try {
cp.open();
for (int i = 0; i < msgs; i++) {
Event e = new EventImpl(("this is a test " + i).getBytes());
cp.append(e);
}
cp.close();
} catch (IOException ioe) {
// This is a semantics change -- no start increments a counter
fail("didn't throw no start exception");
}
}
/**
* error case: no stop message
*
* TODO (jon) There is a possibility that we may leak memory over time if the
* tag fails and is never cleaned up. (e.g. if we fail over to another
* collector, this may stick around). A flush or ageoff is probably needed in
* the long term.
*/
@Test
public void testNoStop() throws IOException {
final int msgs = 100;
MemorySinkSource mss = new MemorySinkSource();
AckChecksumChecker<EventSink> cc = new AckChecksumChecker<EventSink>(mss);
EventSinkDecorator<EventSink> dropStop =
new EventSinkDecorator<EventSink>(cc) {
int drop = msgs + 1; // intial + messages, the drop the last
int count = 0;
public void append(Event e) throws IOException {
if (count == drop) {
count++;
return;
}
count++;
getSink().append(e);
}
};
AckChecksumInjector<EventSink> cp =
new AckChecksumInjector<EventSink>(dropStop);
cp.open();
for (int i = 0; i < msgs; i++) {
Event e = new EventImpl(("this is a test " + i).getBytes());
cp.append(e);
}
cp.close();
// We don't detect an error here..
// TODO (jon) some timeout mechanism?
}
/**
* error case: duplicate/dropped message (bad checksum)
*/
@Test
public void testDupe() throws IOException {
final int msgs = 100;
MemorySinkSource mss = new MemorySinkSource();
AckChecksumChecker<EventSink> cc =
new AckChecksumChecker<EventSink>(mss, new AckListener.Empty() {
@Override
public void err(String group) throws IOException {
throw new IOException("Fail");
}
});
EventSinkDecorator<EventSink> dupeMsg =
new EventSinkDecorator<EventSink>(cc) {
int drop = msgs / 2;
int count = 0;
public void append(Event e) throws IOException {
if (count == drop) {
getSink().append(e); // extra message send.
}
count++;
getSink().append(e);
}
};
AckChecksumInjector<EventSink> cp =
new AckChecksumInjector<EventSink>(dupeMsg);
try {
cp.open();
for (int i = 0; i < 100; i++) {
Event e = new EventImpl(("this is a test " + i).getBytes());
cp.append(e);
}
cp.close();
} catch (IOException ioe) {
return;
}
LOG.info(cc.getReport().toJson());
fail("should have failed");
}
/**
* Make sure the builder works.
*/
@Test
public void testAckInjectorBuilderArgs() throws FlumeSpecException {
FlumeBuilder.buildSink(new Context(), "{ ackInjector => null}");
}
/**
* Throw error when builder encounters too many args.
*/
@Test(expected = FlumeSpecException.class)
public void testAckInjectorBuilderBadArgs() throws FlumeSpecException {
FlumeBuilder.buildSink(new Context(), "{ ackInjector(false) => null}");
}
@Test
public void testAckCheckerBuilderArgs() throws FlumeSpecException {
FlumeBuilder.buildSink(new Context(), "{ackChecker => null}");
}
@Test(expected = FlumeSpecException.class)
public void testAckCheckerBuilderBadArgs() throws FlumeSpecException {
FlumeBuilder.buildSink(new Context(), "{ackChecker(false) => null}");
}
}