/*
* 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.source.taildir;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.apache.flume.Channel;
import org.apache.flume.ChannelSelector;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.Transaction;
import org.apache.flume.channel.ChannelProcessor;
import org.apache.flume.channel.MemoryChannel;
import org.apache.flume.channel.ReplicatingChannelSelector;
import org.apache.flume.conf.Configurables;
import org.apache.flume.lifecycle.LifecycleController;
import org.apache.flume.lifecycle.LifecycleState;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.apache.flume.source.taildir.TaildirSourceConfigurationConstants.FILE_GROUPS;
import static org.apache.flume.source.taildir.TaildirSourceConfigurationConstants.FILE_GROUPS_PREFIX;
import static org.apache.flume.source.taildir.TaildirSourceConfigurationConstants.HEADERS_PREFIX;
import static org.apache.flume.source.taildir.TaildirSourceConfigurationConstants.POSITION_FILE;
import static org.apache.flume.source.taildir.TaildirSourceConfigurationConstants.FILENAME_HEADER;
import static org.apache.flume.source.taildir.TaildirSourceConfigurationConstants.FILENAME_HEADER_KEY;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class TestTaildirSource {
static TaildirSource source;
static MemoryChannel channel;
private File tmpDir;
private String posFilePath;
@Before
public void setUp() {
source = new TaildirSource();
channel = new MemoryChannel();
Configurables.configure(channel, new Context());
List<Channel> channels = new ArrayList<Channel>();
channels.add(channel);
ChannelSelector rcs = new ReplicatingChannelSelector();
rcs.setChannels(channels);
source.setChannelProcessor(new ChannelProcessor(rcs));
tmpDir = Files.createTempDir();
posFilePath = tmpDir.getAbsolutePath() + "/taildir_position_test.json";
}
@After
public void tearDown() {
for (File f : tmpDir.listFiles()) {
f.delete();
}
tmpDir.delete();
}
@Test
public void testRegexFileNameFilteringEndToEnd() throws IOException {
File f1 = new File(tmpDir, "a.log");
File f2 = new File(tmpDir, "a.log.1");
File f3 = new File(tmpDir, "b.log");
File f4 = new File(tmpDir, "c.log.yyyy-MM-01");
File f5 = new File(tmpDir, "c.log.yyyy-MM-02");
Files.write("a.log\n", f1, Charsets.UTF_8);
Files.write("a.log.1\n", f2, Charsets.UTF_8);
Files.write("b.log\n", f3, Charsets.UTF_8);
Files.write("c.log.yyyy-MM-01\n", f4, Charsets.UTF_8);
Files.write("c.log.yyyy-MM-02\n", f5, Charsets.UTF_8);
Context context = new Context();
context.put(POSITION_FILE, posFilePath);
context.put(FILE_GROUPS, "ab c");
// Tail a.log and b.log
context.put(FILE_GROUPS_PREFIX + "ab", tmpDir.getAbsolutePath() + "/[ab].log");
// Tail files that starts with c.log
context.put(FILE_GROUPS_PREFIX + "c", tmpDir.getAbsolutePath() + "/c.log.*");
Configurables.configure(source, context);
source.start();
source.process();
Transaction txn = channel.getTransaction();
txn.begin();
List<String> out = Lists.newArrayList();
for (int i = 0; i < 5; i++) {
Event e = channel.take();
if (e != null) {
out.add(TestTaildirEventReader.bodyAsString(e));
}
}
txn.commit();
txn.close();
assertEquals(4, out.size());
// Make sure we got every file
assertTrue(out.contains("a.log"));
assertFalse(out.contains("a.log.1"));
assertTrue(out.contains("b.log"));
assertTrue(out.contains("c.log.yyyy-MM-01"));
assertTrue(out.contains("c.log.yyyy-MM-02"));
}
@Test
public void testHeaderMapping() throws IOException {
File f1 = new File(tmpDir, "file1");
File f2 = new File(tmpDir, "file2");
File f3 = new File(tmpDir, "file3");
Files.write("file1line1\nfile1line2\n", f1, Charsets.UTF_8);
Files.write("file2line1\nfile2line2\n", f2, Charsets.UTF_8);
Files.write("file3line1\nfile3line2\n", f3, Charsets.UTF_8);
Context context = new Context();
context.put(POSITION_FILE, posFilePath);
context.put(FILE_GROUPS, "f1 f2 f3");
context.put(FILE_GROUPS_PREFIX + "f1", tmpDir.getAbsolutePath() + "/file1$");
context.put(FILE_GROUPS_PREFIX + "f2", tmpDir.getAbsolutePath() + "/file2$");
context.put(FILE_GROUPS_PREFIX + "f3", tmpDir.getAbsolutePath() + "/file3$");
context.put(HEADERS_PREFIX + "f1.headerKeyTest", "value1");
context.put(HEADERS_PREFIX + "f2.headerKeyTest", "value2");
context.put(HEADERS_PREFIX + "f2.headerKeyTest2", "value2-2");
Configurables.configure(source, context);
source.start();
source.process();
Transaction txn = channel.getTransaction();
txn.begin();
for (int i = 0; i < 6; i++) {
Event e = channel.take();
String body = new String(e.getBody(), Charsets.UTF_8);
String headerValue = e.getHeaders().get("headerKeyTest");
String headerValue2 = e.getHeaders().get("headerKeyTest2");
if (body.startsWith("file1")) {
assertEquals("value1", headerValue);
assertNull(headerValue2);
} else if (body.startsWith("file2")) {
assertEquals("value2", headerValue);
assertEquals("value2-2", headerValue2);
} else if (body.startsWith("file3")) {
// No header
assertNull(headerValue);
assertNull(headerValue2);
}
}
txn.commit();
txn.close();
}
@Test
public void testLifecycle() throws IOException, InterruptedException {
File f1 = new File(tmpDir, "file1");
Files.write("file1line1\nfile1line2\n", f1, Charsets.UTF_8);
Context context = new Context();
context.put(POSITION_FILE, posFilePath);
context.put(FILE_GROUPS, "f1");
context.put(FILE_GROUPS_PREFIX + "f1", tmpDir.getAbsolutePath() + "/file1$");
Configurables.configure(source, context);
for (int i = 0; i < 3; i++) {
source.start();
source.process();
assertTrue("Reached start or error", LifecycleController.waitForOneOf(
source, LifecycleState.START_OR_ERROR));
assertEquals("Server is started", LifecycleState.START,
source.getLifecycleState());
source.stop();
assertTrue("Reached stop or error",
LifecycleController.waitForOneOf(source, LifecycleState.STOP_OR_ERROR));
assertEquals("Server is stopped", LifecycleState.STOP,
source.getLifecycleState());
}
}
@Test
public void testFileConsumeOrder() throws IOException {
System.out.println(tmpDir.toString());
// 1) Create 1st file
File f1 = new File(tmpDir, "file1");
String line1 = "file1line1\n";
String line2 = "file1line2\n";
String line3 = "file1line3\n";
Files.write(line1 + line2 + line3, f1, Charsets.UTF_8);
try {
Thread.sleep(1000); // wait before creating a new file
} catch (InterruptedException e) {
}
// 1) Create 2nd file
String line1b = "file2line1\n";
String line2b = "file2line2\n";
String line3b = "file2line3\n";
File f2 = new File(tmpDir, "file2");
Files.write(line1b + line2b + line3b, f2, Charsets.UTF_8);
try {
Thread.sleep(1000); // wait before creating next file
} catch (InterruptedException e) {
}
// 3) Create 3rd file
String line1c = "file3line1\n";
String line2c = "file3line2\n";
String line3c = "file3line3\n";
File f3 = new File(tmpDir, "file3");
Files.write(line1c + line2c + line3c, f3, Charsets.UTF_8);
try {
Thread.sleep(1000); // wait before creating a new file
} catch (InterruptedException e) {
}
// 4) Create 4th file
String line1d = "file4line1\n";
String line2d = "file4line2\n";
String line3d = "file4line3\n";
File f4 = new File(tmpDir, "file4");
Files.write(line1d + line2d + line3d, f4, Charsets.UTF_8);
try {
Thread.sleep(1000); // wait before creating a new file
} catch (InterruptedException e) {
}
// 5) Now update the 3rd file so that its the latest file and gets consumed last
f3.setLastModified(System.currentTimeMillis());
// 4) Consume the files
ArrayList<String> consumedOrder = Lists.newArrayList();
Context context = new Context();
context.put(POSITION_FILE, posFilePath);
context.put(FILE_GROUPS, "g1");
context.put(FILE_GROUPS_PREFIX + "g1", tmpDir.getAbsolutePath() + "/.*");
Configurables.configure(source, context);
source.start();
source.process();
Transaction txn = channel.getTransaction();
txn.begin();
for (int i = 0; i < 12; i++) {
Event e = channel.take();
String body = new String(e.getBody(), Charsets.UTF_8);
consumedOrder.add(body);
}
txn.commit();
txn.close();
System.out.println(consumedOrder);
// 6) Ensure consumption order is in order of last update time
ArrayList<String> expected = Lists.newArrayList(line1, line2, line3, // file1
line1b, line2b, line3b, // file2
line1d, line2d, line3d, // file4
line1c, line2c, line3c // file3
);
for (int i = 0; i != expected.size(); ++i) {
expected.set(i, expected.get(i).trim());
}
assertArrayEquals("Files not consumed in expected order", expected.toArray(),
consumedOrder.toArray());
}
@Test
public void testPutFilenameHeader() throws IOException {
File f1 = new File(tmpDir, "file1");
Files.write("f1\n", f1, Charsets.UTF_8);
Context context = new Context();
context.put(POSITION_FILE, posFilePath);
context.put(FILE_GROUPS, "fg");
context.put(FILE_GROUPS_PREFIX + "fg", tmpDir.getAbsolutePath() + "/file.*");
context.put(FILENAME_HEADER, "true");
context.put(FILENAME_HEADER_KEY, "path");
Configurables.configure(source, context);
source.start();
source.process();
Transaction txn = channel.getTransaction();
txn.begin();
Event e = channel.take();
txn.commit();
txn.close();
assertNotNull(e.getHeaders().get("path"));
assertEquals(f1.getAbsolutePath(),
e.getHeaders().get("path"));
}
}