/**
* 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.agent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.Context;
import com.cloudera.flume.conf.FlumeBuilder;
import com.cloudera.flume.conf.FlumeConfiguration;
import com.cloudera.flume.conf.FlumeSpecException;
import com.cloudera.flume.conf.LogicalNodeContext;
import com.cloudera.flume.conf.ReportTestingContext;
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.EventUtil;
import com.cloudera.flume.handlers.debug.ConsoleEventSink;
import com.cloudera.flume.handlers.debug.MemorySinkSource;
import com.cloudera.flume.handlers.endtoend.AckChecksumInjector;
import com.cloudera.flume.handlers.endtoend.AckListener;
import com.cloudera.flume.reporter.ReportManager;
import com.cloudera.flume.reporter.aggregator.CounterSink;
import com.cloudera.util.FileUtil;
/**
* Tests WriteAheadLogDeco's builder, multiple open close, and actual behavior.
*/
public class TestAckedWALDecorator {
static final Logger LOG = LoggerFactory
.getLogger(TestAckedWALDecorator.class);
File tmpdir = null;
FlumeNode node;
MockMasterRPC mock;
@Before
public void setUp() {
// change config so that the write ahead log dir is in a new uniq place
try {
tmpdir = FileUtil.mktempdir();
} catch (Exception e) {
fail("mk temp dir failed");
}
FlumeConfiguration conf = FlumeConfiguration.get();
conf.set(FlumeConfiguration.AGENT_LOG_DIR_NEW, tmpdir.getAbsolutePath());
// This will register the FlumeNode with a MockMasterRPC so it doesn't go
// across the network
mock = new MockMasterRPC();
node = new FlumeNode(mock, false /* starthttp */, false /* oneshot */);
}
@After
public void tearDown() {
try {
FileUtil.rmr(tmpdir);
} catch (IOException e) {
LOG.error("Failed to remove dir " + tmpdir, e);
}
}
@Test
public void testBuilder() throws FlumeSpecException {
String cfg = " { ackedWriteAhead => null}";
FlumeBuilder.buildSink(LogicalNodeContext.testingContext(), cfg);
String cfg1 = "{ ackedWriteAhead(15000) => null}";
FlumeBuilder.buildSink(LogicalNodeContext.testingContext(), cfg1);
String cfg4 = "{ ackedWriteAhead(\"failurama\") => null}";
try {
FlumeBuilder.buildSink(LogicalNodeContext.testingContext(), cfg4);
} catch (Exception e) {
return;
}
fail("unexpected fall through");
}
@Test
public void testOpenClose() throws IOException, FlumeSpecException,
InterruptedException {
String rpt = "foo";
String snk = " { ackedWriteAhead(100) => [console, counter(\"" + rpt
+ "\") ] } ";
for (int i = 0; i < 100; i++) {
EventSink es = FlumeBuilder.buildSink(
LogicalNodeContext.testingContext(), snk);
es.open();
es.close();
}
}
/**
* This is a trickier test case. We create a console/counter sink that has a
* ackedWriteAhead in front of it (aiming for 100 ms per batch). All events
* should make it through after a slight delay.
*/
@Test
public void testBehavior() throws FlumeSpecException, InterruptedException,
IOException {
int count = 100;
String rpt = "foo";
// in a real situation, there would be a agent sink after the
// ackedWriteAhead and a collectorSource before the ackChecker block.
String snk = " { ackedWriteAhead(1000) => { ackChecker => counter(\"" + rpt
+ "\") } } ";
EventSink es = FlumeBuilder.buildSink(new ReportTestingContext(
LogicalNodeContext.testingContext()), snk);
es.open();
for (int i = 0; i < count; i++) {
Event e = new EventImpl(("test message " + i).getBytes());
es.append(e);
}
// last batch doesn't flush automatically unless time passes or closed-- one
// pending
Set<String> pending = mock.ackman.getPending();
assertEquals(0, pending.size());
// close and flush
es.close();
node.getAckChecker().checkAcks();
CounterSink ctr = (CounterSink) ReportManager.get().getReportable(rpt);
assertEquals(count, ctr.getCount());
// check master state
mock.ackman.dumpLog();
Set<String> pending2 = mock.ackman.getPending();
assertEquals(0, pending2.size());
}
/**
* This test case does something to force a retransmit attempt
*
* @throws InterruptedException
*/
@SuppressWarnings("unchecked")
@Test
public void testForceRetransmit() throws FlumeSpecException, IOException,
InterruptedException {
// This will register the FlumeNode with a MockMasterRPC so it doesn't go
// across the network
MockMasterRPC mock = new MockMasterRPC();
FlumeNode node = new FlumeNode(mock, false /* starthttp */, false /* onshot */);
AckListener pending = node.getAckChecker().getAgentAckQueuer();
// initial source of data.
MemorySinkSource mem = new MemorySinkSource();
byte[] tag = "my tag".getBytes();
AckChecksumInjector<EventSink> ackinj = new AckChecksumInjector<EventSink>(
mem, tag, pending);
ackinj.open();
for (int i = 0; i < 5; i++) {
ackinj.append(new EventImpl(("Event " + i).getBytes()));
}
ackinj.close();
// there now are now 7 events in the mem
// consume data.
EventSink es1 = new ConsoleEventSink();
EventUtil.dumpAll(mem, es1);
System.out.println("---");
String rpt = "foo";
String snk = " { intervalDroppyAppend(5) => { ackChecker => [console, counter(\""
+ rpt + "\") ] } } ";
EventSink es = FlumeBuilder.buildSink(new Context(), snk);
mem.open(); // resets index.
es.open();
EventUtil.dumpAll(mem, es);
node.getAckChecker().checkAcks();
assertEquals(1, node.getAckChecker().pending.size());
mem.open(); // resets index.
// send to collector, collector updates master state (bypassing flakeyness)
EventUtil.dumpAll(mem, ((EventSinkDecorator<EventSink>) es).getSink());
// agent gets state from master, updates it ack states
try {
node.getAckChecker().checkAcks();
} catch (RuntimeException npe) {
// this actually goes an trys to delete a file, but since this is fake it
// doesn't exist
LOG.info(npe.getMessage());
}
assertEquals(0, node.getAckChecker().pending.size());
}
}