/*
* 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.nifi.processors.standard;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.util.put.sender.ChannelSender;
import org.apache.nifi.provenance.ProvenanceEventRecord;
import org.apache.nifi.provenance.ProvenanceEventType;
import org.apache.nifi.ssl.SSLContextService;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestPutSyslog {
private MockCollectingSender sender;
private MockPutSyslog proc;
private TestRunner runner;
@Before
public void setup() throws IOException {
sender = new MockCollectingSender();
proc = new MockPutSyslog(sender);
runner = TestRunners.newTestRunner(proc);
runner.setProperty(PutSyslog.HOSTNAME, "localhost");
runner.setProperty(PutSyslog.PORT, "12345");
}
@Test
public void testValidMessageStaticPropertiesUdp() {
final String pri = "34";
final String version = "1";
final String stamp = "2003-10-11T22:14:15.003Z";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
final String expectedMessage = "<" + pri + ">" + version + " " + stamp + " " + host + " " + body;
runner.setProperty(PutSyslog.MSG_PRIORITY, pri);
runner.setProperty(PutSyslog.MSG_VERSION, version);
runner.setProperty(PutSyslog.MSG_TIMESTAMP, stamp);
runner.setProperty(PutSyslog.MSG_HOSTNAME, host);
runner.setProperty(PutSyslog.MSG_BODY, body);
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 1);
Assert.assertEquals(1, sender.messages.size());
Assert.assertEquals(expectedMessage, sender.messages.get(0));
final List<ProvenanceEventRecord> events = runner.getProvenanceEvents();
Assert.assertNotNull(events);
Assert.assertEquals(1, events.size());
final ProvenanceEventRecord event = events.get(0);
Assert.assertEquals(ProvenanceEventType.SEND, event.getEventType());
Assert.assertEquals("UDP://localhost:12345", event.getTransitUri());
}
@Test
public void testValidMessageStaticPropertiesTcp() {
final String pri = "34";
final String version = "1";
final String stamp = "2003-10-11T22:14:15.003Z";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
final String expectedMessage = "<" + pri + ">" + version + " " + stamp + " " + host + " " + body;
runner.setProperty(PutSyslog.PROTOCOL, PutSyslog.TCP_VALUE);
runner.setProperty(PutSyslog.MSG_PRIORITY, pri);
runner.setProperty(PutSyslog.MSG_VERSION, version);
runner.setProperty(PutSyslog.MSG_TIMESTAMP, stamp);
runner.setProperty(PutSyslog.MSG_HOSTNAME, host);
runner.setProperty(PutSyslog.MSG_BODY, body);
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 1);
Assert.assertEquals(1, sender.messages.size());
Assert.assertEquals(expectedMessage, sender.messages.get(0).replace("\n", ""));
final List<ProvenanceEventRecord> events = runner.getProvenanceEvents();
Assert.assertNotNull(events);
Assert.assertEquals(1, events.size());
final ProvenanceEventRecord event = events.get(0);
Assert.assertEquals(ProvenanceEventType.SEND, event.getEventType());
Assert.assertEquals("TCP://localhost:12345", event.getTransitUri());
}
@Test
public void testValidMessageStaticPropertiesNoVersion() {
final String pri = "34";
final String stamp = "2003-10-11T22:14:15.003Z";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
final String expectedMessage = "<" + pri + ">" + stamp + " " + host + " " + body;
runner.setProperty(PutSyslog.MSG_PRIORITY, pri);
runner.setProperty(PutSyslog.MSG_TIMESTAMP, stamp);
runner.setProperty(PutSyslog.MSG_HOSTNAME, host);
runner.setProperty(PutSyslog.MSG_BODY, body);
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 1);
Assert.assertEquals(1, sender.messages.size());
Assert.assertEquals(expectedMessage, sender.messages.get(0));
}
@Test
public void testValidMessageELProperties() {
final String pri = "34";
final String stamp = "2003-10-11T22:14:15.003Z";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
final String expectedMessage = "<" + pri + ">" + stamp + " " + host + " " + body;
runner.setProperty(PutSyslog.MSG_PRIORITY, "${syslog.priority}");
runner.setProperty(PutSyslog.MSG_TIMESTAMP, "${syslog.timestamp}");
runner.setProperty(PutSyslog.MSG_HOSTNAME, "${syslog.hostname}");
runner.setProperty(PutSyslog.MSG_BODY, "${syslog.body}");
final Map<String,String> attributes = new HashMap<>();
attributes.put("syslog.priority", pri);
attributes.put("syslog.timestamp", stamp);
attributes.put("syslog.hostname", host);
attributes.put("syslog.body", body);
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")), attributes);
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 1);
Assert.assertEquals(1, sender.messages.size());
Assert.assertEquals(expectedMessage, sender.messages.get(0));
}
@Test
public void testInvalidMessageELProperties() {
final String pri = "34";
final String stamp = "not-a-timestamp";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
runner.setProperty(PutSyslog.MSG_PRIORITY, "${syslog.priority}");
runner.setProperty(PutSyslog.MSG_TIMESTAMP, "${syslog.timestamp}");
runner.setProperty(PutSyslog.MSG_HOSTNAME, "${syslog.hostname}");
runner.setProperty(PutSyslog.MSG_BODY, "${syslog.body}");
final Map<String,String> attributes = new HashMap<>();
attributes.put("syslog.priority", pri);
attributes.put("syslog.timestamp", stamp);
attributes.put("syslog.hostname", host);
attributes.put("syslog.body", body);
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")), attributes);
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_INVALID, 1);
Assert.assertEquals(0, sender.messages.size());
}
@Test
public void testIOExceptionOnSend() throws IOException {
final String pri = "34";
final String version = "1";
final String stamp = "2003-10-11T22:14:15.003Z";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
proc = new MockPutSyslog(new MockErrorSender());
runner = TestRunners.newTestRunner(proc);
runner.setProperty(PutSyslog.HOSTNAME, "localhost");
runner.setProperty(PutSyslog.PORT, "12345");
runner.setProperty(PutSyslog.MSG_PRIORITY, pri);
runner.setProperty(PutSyslog.MSG_VERSION, version);
runner.setProperty(PutSyslog.MSG_TIMESTAMP, stamp);
runner.setProperty(PutSyslog.MSG_HOSTNAME, host);
runner.setProperty(PutSyslog.MSG_BODY, body);
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_FAILURE, 1);
Assert.assertEquals(0, sender.messages.size());
}
@Test
public void testIOExceptionCreatingConnection() throws IOException {
final String pri = "34";
final String version = "1";
final String stamp = "2003-10-11T22:14:15.003Z";
final String host = "mymachine.example.com";
final String body = "su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8";
Processor proc = new MockCreationErrorPutSyslog(new MockErrorSender(), 1);
runner = TestRunners.newTestRunner(proc);
runner.setProperty(PutSyslog.HOSTNAME, "localhost");
runner.setProperty(PutSyslog.PORT, "12345");
runner.setProperty(PutSyslog.BATCH_SIZE, "1");
runner.setProperty(PutSyslog.MSG_PRIORITY, pri);
runner.setProperty(PutSyslog.MSG_VERSION, version);
runner.setProperty(PutSyslog.MSG_TIMESTAMP, stamp);
runner.setProperty(PutSyslog.MSG_HOSTNAME, host);
runner.setProperty(PutSyslog.MSG_BODY, body);
// the first run will throw IOException when calling send so the connection won't be re-qeued
// the second run will try to create a new connection but throw an exception which should be caught and route files to failure
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.run(2);
runner.assertAllFlowFilesTransferred(PutSyslog.REL_FAILURE, 2);
Assert.assertEquals(0, sender.messages.size());
}
@Test
public void testLargeMessageFailure() {
final String pri = "34";
final String stamp = "2015-10-15T22:14:15.003Z";
final String host = "mymachine.example.com";
final StringBuilder bodyBuilder = new StringBuilder(4096);
for (int i=0; i < 4096; i++) {
bodyBuilder.append("a");
}
runner.setProperty(PutSyslog.MSG_PRIORITY, "${syslog.priority}");
runner.setProperty(PutSyslog.MSG_TIMESTAMP, "${syslog.timestamp}");
runner.setProperty(PutSyslog.MSG_HOSTNAME, "${syslog.hostname}");
runner.setProperty(PutSyslog.MSG_BODY, "${syslog.body}");
final Map<String,String> attributes = new HashMap<>();
attributes.put("syslog.priority", pri);
attributes.put("syslog.timestamp", stamp);
attributes.put("syslog.hostname", host);
attributes.put("syslog.body", bodyBuilder.toString());
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")), attributes);
runner.run();
// should have dynamically created a larger buffer
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 1);
Assert.assertEquals(1, sender.messages.size());
}
@Test
public void testNoIncomingData() {
runner.setProperty(PutSyslog.MSG_PRIORITY, "10");
runner.setProperty(PutSyslog.MSG_VERSION, "1");
runner.setProperty(PutSyslog.MSG_TIMESTAMP, "2003-10-11T22:14:15.003Z");
runner.setProperty(PutSyslog.MSG_HOSTNAME, "localhost");
runner.setProperty(PutSyslog.MSG_BODY, "test");
// queue one file but run several times to test no incoming data
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
runner.run(5);
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 1);
}
@Test
public void testBatchingFlowFiles() {
runner.setProperty(PutSyslog.BATCH_SIZE, "10");
runner.setProperty(PutSyslog.MSG_PRIORITY, "${syslog.priority}");
runner.setProperty(PutSyslog.MSG_TIMESTAMP, "${syslog.timestamp}");
runner.setProperty(PutSyslog.MSG_HOSTNAME, "${syslog.hostname}");
runner.setProperty(PutSyslog.MSG_BODY, "${syslog.body}");
final Map<String,String> attributes = new HashMap<>();
attributes.put("syslog.priority", "10");
attributes.put("syslog.timestamp", "2015-10-11T22:14:15.003Z");
attributes.put("syslog.hostname", "my.host.name");
attributes.put("syslog.body", "blah blah blah");
for (int i=0; i < 15; i++) {
runner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")), attributes);
}
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 10);
Assert.assertEquals(10, sender.messages.size());
runner.run();
runner.assertAllFlowFilesTransferred(PutSyslog.REL_SUCCESS, 15);
Assert.assertEquals(15, sender.messages.size());
}
// Mock processor to return a MockCollectingSender
static class MockPutSyslog extends PutSyslog {
ChannelSender mockSender;
public MockPutSyslog(ChannelSender sender) {
this.mockSender = sender;
}
@Override
protected ChannelSender createSender(SSLContextService sslContextService, String protocol, String host,
int port, int maxSendBuffer, int timeout)
throws IOException {
return mockSender;
}
}
// Mock processor to test exception when creating new senders
static class MockCreationErrorPutSyslog extends PutSyslog {
int numSendersCreated;
int numSendersAllowed;
ChannelSender mockSender;
public MockCreationErrorPutSyslog(ChannelSender sender, int numSendersAllowed) {
this.mockSender = sender;
this.numSendersAllowed = numSendersAllowed;
}
@Override
protected ChannelSender createSender(SSLContextService sslContextService, String protocol, String host,
int port, int maxSendBuffer, int timeout)
throws IOException {
if (numSendersCreated >= numSendersAllowed) {
throw new IOException("too many senders");
}
numSendersCreated++;
return mockSender;
}
}
// Mock sender that saves any messages passed to send()
static class MockCollectingSender extends ChannelSender {
List<String> messages = new ArrayList<>();
public MockCollectingSender() throws IOException {
super("myhost", 0, 0, null);
}
@Override
public void open() throws IOException {
}
@Override
public void send(String message, Charset charset) throws IOException {
messages.add(message);
super.send(message, charset);
}
@Override
protected void write(byte[] buffer) throws IOException {
}
@Override
public boolean isConnected() {
return true;
}
@Override
public void close() {
}
}
// Mock sender that throws IOException on calls to write() or send()
static class MockErrorSender extends ChannelSender {
public MockErrorSender() throws IOException {
super(null, 0, 0, null);
}
@Override
public void open() throws IOException {
}
@Override
public void send(String message, Charset charset) throws IOException {
throw new IOException("error");
}
@Override
protected void write(byte[] data) throws IOException {
throw new IOException("error");
}
@Override
public boolean isConnected() {
return false;
}
@Override
public void close() {
}
}
}