/*
* 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.cassandra;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Configuration;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import com.datastax.driver.core.exceptions.ReadTimeoutException;
import org.apache.avro.Schema;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.stream.io.ByteArrayOutputStream;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.junit.Before;
import org.junit.Test;
import javax.net.ssl.SSLContext;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class QueryCassandraTest {
private TestRunner testRunner;
private MockQueryCassandra processor;
@Before
public void setUp() throws Exception {
processor = new MockQueryCassandra();
testRunner = TestRunners.newTestRunner(processor);
}
@Test
public void testProcessorConfigValid() {
testRunner.setProperty(AbstractCassandraProcessor.CONSISTENCY_LEVEL, "ONE");
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "localhost:9042");
testRunner.assertNotValid();
testRunner.setProperty(QueryCassandra.CQL_SELECT_QUERY, "select * from test");
testRunner.assertValid();
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "password");
testRunner.assertNotValid();
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "username");
testRunner.assertValid();
}
@Test
public void testProcessorELConfigValid() {
testRunner.setProperty(AbstractCassandraProcessor.CONSISTENCY_LEVEL, "ONE");
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "${hosts}");
testRunner.setProperty(QueryCassandra.CQL_SELECT_QUERY, "${query}");
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "${pass}");
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "${user}");
testRunner.assertValid();
}
@Test
public void testProcessorNoInputFlowFileAndExceptions() {
setUpStandardProcessorConfig();
// Test no input flowfile
testRunner.setIncomingConnection(false);
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_SUCCESS, 1);
testRunner.clearTransferState();
// Test exceptions
processor.setExceptionToThrow(new NoHostAvailableException(new HashMap<InetSocketAddress, Throwable>()));
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_RETRY, 1);
testRunner.clearTransferState();
processor.setExceptionToThrow(
new ReadTimeoutException(new InetSocketAddress("localhost", 9042), ConsistencyLevel.ANY, 0, 1, false));
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_RETRY, 1);
testRunner.clearTransferState();
processor.setExceptionToThrow(
new InvalidQueryException(new InetSocketAddress("localhost", 9042), "invalid query"));
testRunner.run(1, true, true);
// No files transferred to failure if there was no incoming connection
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_FAILURE, 0);
testRunner.clearTransferState();
processor.setExceptionToThrow(new ProcessException());
testRunner.run(1, true, true);
// No files transferred to failure if there was no incoming connection
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_FAILURE, 0);
testRunner.clearTransferState();
processor.setExceptionToThrow(null);
}
@Test
public void testProcessorJsonOutput() {
setUpStandardProcessorConfig();
testRunner.setIncomingConnection(false);
// Test JSON output
testRunner.setProperty(QueryCassandra.OUTPUT_FORMAT, QueryCassandra.JSON_FORMAT);
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_SUCCESS, 1);
List<MockFlowFile> files = testRunner.getFlowFilesForRelationship(QueryCassandra.REL_SUCCESS);
assertNotNull(files);
assertEquals("One file should be transferred to success", 1, files.size());
assertEquals("{\"results\":[{\"user_id\":\"user1\",\"first_name\":\"Joe\",\"last_name\":\"Smith\","
+ "\"emails\":[\"jsmith@notareal.com\"],\"top_places\":[\"New York, NY\",\"Santa Clara, CA\"],"
+ "\"todo\":{\"2016-01-03 05:00:00+0000\":\"Set my alarm for a month from now\"},"
+ "\"registered\":\"false\",\"scale\":1.0,\"metric\":2.0},"
+ "{\"user_id\":\"user2\",\"first_name\":\"Mary\",\"last_name\":\"Jones\","
+ "\"emails\":[\"mjones@notareal.com\"],\"top_places\":[\"Orlando, FL\"],"
+ "\"todo\":{\"2016-02-03 05:00:00+0000\":\"Get milk and bread\"},"
+ "\"registered\":\"true\",\"scale\":3.0,\"metric\":4.0}]}",
new String(files.get(0).toByteArray()));
}
@Test
public void testProcessorELConfigJsonOutput() {
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "${hosts}");
testRunner.setProperty(QueryCassandra.CQL_SELECT_QUERY, "${query}");
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "${pass}");
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "${user}");
testRunner.setProperty(AbstractCassandraProcessor.CHARSET, "${charset}");
testRunner.setProperty(QueryCassandra.QUERY_TIMEOUT, "${timeout}");
testRunner.setProperty(QueryCassandra.FETCH_SIZE, "${fetch}");
testRunner.setIncomingConnection(false);
testRunner.assertValid();
testRunner.setVariable("hosts", "localhost:9042");
testRunner.setVariable("user", "username");
testRunner.setVariable("pass", "password");
testRunner.setVariable("charset", "UTF-8");
testRunner.setVariable("timeout", "30 sec");
testRunner.setVariable("fetch", "0");
// Test JSON output
testRunner.setProperty(QueryCassandra.OUTPUT_FORMAT, QueryCassandra.JSON_FORMAT);
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_SUCCESS, 1);
List<MockFlowFile> files = testRunner.getFlowFilesForRelationship(QueryCassandra.REL_SUCCESS);
assertNotNull(files);
assertEquals("One file should be transferred to success", 1, files.size());
assertEquals("{\"results\":[{\"user_id\":\"user1\",\"first_name\":\"Joe\",\"last_name\":\"Smith\","
+ "\"emails\":[\"jsmith@notareal.com\"],\"top_places\":[\"New York, NY\",\"Santa Clara, CA\"],"
+ "\"todo\":{\"2016-01-03 05:00:00+0000\":\"Set my alarm for a month from now\"},"
+ "\"registered\":\"false\",\"scale\":1.0,\"metric\":2.0},"
+ "{\"user_id\":\"user2\",\"first_name\":\"Mary\",\"last_name\":\"Jones\","
+ "\"emails\":[\"mjones@notareal.com\"],\"top_places\":[\"Orlando, FL\"],"
+ "\"todo\":{\"2016-02-03 05:00:00+0000\":\"Get milk and bread\"},"
+ "\"registered\":\"true\",\"scale\":3.0,\"metric\":4.0}]}",
new String(files.get(0).toByteArray()));
}
@Test
public void testProcessorJsonOutputWithQueryTimeout() {
setUpStandardProcessorConfig();
testRunner.setProperty(QueryCassandra.QUERY_TIMEOUT, "5 sec");
testRunner.setIncomingConnection(false);
// Test JSON output
testRunner.setProperty(QueryCassandra.OUTPUT_FORMAT, QueryCassandra.JSON_FORMAT);
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_SUCCESS, 1);
List<MockFlowFile> files = testRunner.getFlowFilesForRelationship(QueryCassandra.REL_SUCCESS);
assertNotNull(files);
assertEquals("One file should be transferred to success", 1, files.size());
}
@Test
public void testProcessorEmptyFlowFileAndExceptions() {
setUpStandardProcessorConfig();
// Run with empty flowfile
testRunner.setIncomingConnection(true);
processor.setExceptionToThrow(null);
testRunner.enqueue("".getBytes());
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_SUCCESS, 1);
testRunner.clearTransferState();
// Test exceptions
processor.setExceptionToThrow(new NoHostAvailableException(new HashMap<InetSocketAddress, Throwable>()));
testRunner.enqueue("".getBytes());
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_RETRY, 1);
testRunner.clearTransferState();
processor.setExceptionToThrow(
new ReadTimeoutException(new InetSocketAddress("localhost", 9042), ConsistencyLevel.ANY, 0, 1, false));
testRunner.enqueue("".getBytes());
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_RETRY, 1);
testRunner.clearTransferState();
processor.setExceptionToThrow(
new InvalidQueryException(new InetSocketAddress("localhost", 9042), "invalid query"));
testRunner.enqueue("".getBytes());
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_FAILURE, 1);
testRunner.clearTransferState();
processor.setExceptionToThrow(new ProcessException());
testRunner.enqueue("".getBytes());
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(QueryCassandra.REL_FAILURE, 1);
}
@Test
public void testCreateSchema() throws Exception {
ResultSet rs = CassandraQueryTestUtil.createMockResultSet();
Schema schema = QueryCassandra.createSchema(rs);
assertNotNull(schema);
assertEquals(Schema.Type.RECORD, schema.getType());
// Check record fields, starting with user_id
Schema.Field field = schema.getField("user_id");
assertNotNull(field);
Schema fieldSchema = field.schema();
Schema.Type type = fieldSchema.getType();
assertEquals(Schema.Type.UNION, type);
// Assert individual union types, first is null
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.STRING, fieldSchema.getTypes().get(1).getType());
field = schema.getField("first_name");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
assertEquals(Schema.Type.UNION, type);
// Assert individual union types, first is null
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.STRING, fieldSchema.getTypes().get(1).getType());
field = schema.getField("last_name");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
assertEquals(Schema.Type.UNION, type);
// Assert individual union types, first is null
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.STRING, fieldSchema.getTypes().get(1).getType());
field = schema.getField("emails");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
// Should be a union of null and array
assertEquals(Schema.Type.UNION, type);
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.ARRAY, fieldSchema.getTypes().get(1).getType());
Schema arraySchema = fieldSchema.getTypes().get(1);
// Assert individual array element types are unions of null and String
Schema elementSchema = arraySchema.getElementType();
assertEquals(Schema.Type.UNION, elementSchema.getType());
assertEquals(Schema.Type.NULL, elementSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.STRING, elementSchema.getTypes().get(1).getType());
field = schema.getField("top_places");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
// Should be a union of null and array
assertEquals(Schema.Type.UNION, type);
assertEquals(Schema.Type.ARRAY, fieldSchema.getTypes().get(1).getType());
arraySchema = fieldSchema.getTypes().get(1);
// Assert individual array element types are unions of null and String
elementSchema = arraySchema.getElementType();
assertEquals(Schema.Type.UNION, elementSchema.getType());
assertEquals(Schema.Type.NULL, elementSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.STRING, elementSchema.getTypes().get(1).getType());
field = schema.getField("todo");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
// Should be a union of null and map
assertEquals(Schema.Type.UNION, type);
assertEquals(Schema.Type.MAP, fieldSchema.getTypes().get(1).getType());
Schema mapSchema = fieldSchema.getTypes().get(1);
// Assert individual map value types are unions of null and String
Schema valueSchema = mapSchema.getValueType();
assertEquals(Schema.Type.NULL, valueSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.STRING, valueSchema.getTypes().get(1).getType());
field = schema.getField("registered");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
assertEquals(Schema.Type.UNION, type);
// Assert individual union types, first is null
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.BOOLEAN, fieldSchema.getTypes().get(1).getType());
field = schema.getField("scale");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
assertEquals(Schema.Type.UNION, type);
// Assert individual union types, first is null
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.FLOAT, fieldSchema.getTypes().get(1).getType());
field = schema.getField("metric");
assertNotNull(field);
fieldSchema = field.schema();
type = fieldSchema.getType();
assertEquals(Schema.Type.UNION, type);
// Assert individual union types, first is null
assertEquals(Schema.Type.NULL, fieldSchema.getTypes().get(0).getType());
assertEquals(Schema.Type.DOUBLE, fieldSchema.getTypes().get(1).getType());
}
@Test
public void testConvertToAvroStream() throws Exception {
ResultSet rs = CassandraQueryTestUtil.createMockResultSet();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
long numberOfRows = QueryCassandra.convertToAvroStream(rs, baos, 0, null);
assertEquals(2, numberOfRows);
}
@Test
public void testConvertToJSONStream() throws Exception {
ResultSet rs = CassandraQueryTestUtil.createMockResultSet();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
long numberOfRows = QueryCassandra.convertToJsonStream(rs, baos, StandardCharsets.UTF_8, 0, null);
assertEquals(2, numberOfRows);
}
private void setUpStandardProcessorConfig() {
testRunner.setProperty(AbstractCassandraProcessor.CONSISTENCY_LEVEL, "ONE");
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "localhost:9042");
testRunner.setProperty(QueryCassandra.CQL_SELECT_QUERY, "select * from test");
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "password");
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "username");
}
/**
* Provides a stubbed processor instance for testing
*/
private static class MockQueryCassandra extends QueryCassandra {
private Exception exceptionToThrow = null;
@Override
protected Cluster createCluster(List<InetSocketAddress> contactPoints, SSLContext sslContext,
String username, String password) {
Cluster mockCluster = mock(Cluster.class);
try {
Metadata mockMetadata = mock(Metadata.class);
when(mockMetadata.getClusterName()).thenReturn("cluster1");
when(mockCluster.getMetadata()).thenReturn(mockMetadata);
Session mockSession = mock(Session.class);
when(mockCluster.connect()).thenReturn(mockSession);
when(mockCluster.connect(anyString())).thenReturn(mockSession);
Configuration config = Configuration.builder().build();
when(mockCluster.getConfiguration()).thenReturn(config);
ResultSetFuture future = mock(ResultSetFuture.class);
ResultSet rs = CassandraQueryTestUtil.createMockResultSet();
when(future.getUninterruptibly()).thenReturn(rs);
try {
doReturn(rs).when(future).getUninterruptibly(anyLong(), any(TimeUnit.class));
} catch (TimeoutException te) {
throw new IllegalArgumentException("Mocked cluster doesn't time out");
}
if (exceptionToThrow != null) {
when(mockSession.executeAsync(anyString())).thenThrow(exceptionToThrow);
} else {
when(mockSession.executeAsync(anyString())).thenReturn(future);
}
} catch (Exception e) {
fail(e.getMessage());
}
return mockCluster;
}
public void setExceptionToThrow(Exception e) {
this.exceptionToThrow = e;
}
}
}