/* * Copyright 2014-2016 CyberVision, Inc. * * Licensed 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.kaaproject.kaa.server.appenders.cassandra.appender; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.querybuilder.QueryBuilder; import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.kaaproject.kaa.common.avro.AvroByteArrayConverter; import org.kaaproject.kaa.common.dto.EndpointProfileDataDto; import org.kaaproject.kaa.common.dto.logs.LogAppenderDto; import org.kaaproject.kaa.common.dto.logs.LogHeaderStructureDto; import org.kaaproject.kaa.common.dto.logs.LogSchemaDto; import org.kaaproject.kaa.server.appenders.cassandra.appender.gen.Level; import org.kaaproject.kaa.server.appenders.cassandra.appender.gen.LogData; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.CassandraBatchType; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.CassandraConfig; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.CassandraExecuteRequestType; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.CassandraServer; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.ClusteringElement; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.ColumnMappingElement; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.ColumnMappingElementType; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.ColumnType; import org.kaaproject.kaa.server.appenders.cassandra.config.gen.OrderType; import org.kaaproject.kaa.server.common.CustomCassandraCQLUnit; import org.kaaproject.kaa.server.common.log.shared.appender.LogAppender; import org.kaaproject.kaa.server.common.log.shared.appender.LogDeliveryCallback; import org.kaaproject.kaa.server.common.log.shared.appender.LogEvent; import org.kaaproject.kaa.server.common.log.shared.appender.LogSchema; import org.kaaproject.kaa.server.common.log.shared.appender.data.BaseLogEventPack; import org.kaaproject.kaa.server.common.log.shared.appender.data.BaseProfileInfo; import org.kaaproject.kaa.server.common.log.shared.appender.data.BaseSchemaInfo; import org.kaaproject.kaa.server.common.log.shared.avro.gen.RecordHeader; import org.springframework.test.util.ReflectionTestUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; public class CassandraLogAppenderTest { public static final String KEY_SPACE_NAME = "kaa_test"; private static final String SERVER_PROFILE_SCHEMA_FILE = "server_profile_schema.avsc"; private static final String SERVER_PROFILE_CONTENT_FILE = "server_profile_content.json"; private static final Random RANDOM = new Random(); @ClassRule public static CustomCassandraCQLUnit cassandraUnit = new CustomCassandraCQLUnit(new ClassPathCQLDataSet("appender_test.cql", false, false), "cassandra-test.yaml", 4 * 60000L); private LogAppender logAppender; private LogAppenderDto appenderDto; private CassandraConfig configuration; private RecordHeader header; private String appToken; private String endpointKeyHash; private EndpointProfileDataDto profileDto; private AvroByteArrayConverter<LogData> logDataConverter = new AvroByteArrayConverter<>(LogData.class); @Before public void beforeTest() throws IOException { this.initLogAppender(false); } @Test public void doAppendTest() throws IOException, InterruptedException { DeliveryCallback callback = new DeliveryCallback(); logAppender.doAppend(generateLogEventPack(20, true), callback); Thread.sleep(3000); CassandraLogEventDao logEventDao = (CassandraLogEventDao) ReflectionTestUtils.getField(logAppender, "logEventDao"); Session session = (Session) ReflectionTestUtils.getField(logEventDao, "session"); ResultSet resultSet = session.execute(QueryBuilder.select().countAll() .from(KEY_SPACE_NAME, "logs_" + appToken + "_" + Math.abs(configuration.hashCode()))); Row row = resultSet.one(); Assert.assertEquals(20L, row.getLong(0)); Assert.assertEquals(1, callback.getSuccessCount()); } /** * Tests that log records cannot be appended if they lack a server field * mapped by the appender. */ @Test public void doAppendNegativeTest() throws Exception { this.initLogAppender(true); this.logAppender.doAppend(this.generateLogEventPack(5, false), new DeliveryCallback()); /* * Check that a server error has occured because no server profile is * specified for this log pack. */ Thread.sleep(3000); CassandraLogEventDao logEventDao = (CassandraLogEventDao) ReflectionTestUtils.getField(this.logAppender, "logEventDao"); Session session = (Session) ReflectionTestUtils.getField(logEventDao, "session"); String table = "logs_" + this.appToken + "_" + Math.abs(this.configuration.hashCode()); /* * Nothing has been saved because of the error. */ Row count = session.execute(QueryBuilder.select().countAll().from(KEY_SPACE_NAME, table)).one(); Assert.assertEquals(0L, count.getLong(0)); } private BaseLogEventPack generateLogEventPack(int count, boolean includeServerProfile) throws IOException { List<LogEvent> events = new ArrayList<>(count); for (int i = 0; i < count; i++) { LogEvent event = new LogEvent(); event.setLogData(logDataConverter.toByteArray(new LogData(Level.DEBUG, UUID.randomUUID().toString()))); events.add(event); } BaseLogEventPack logEventPack = new BaseLogEventPack(profileDto, System.currentTimeMillis(), 2, events); LogSchemaDto logSchemaDto = new LogSchemaDto(); logSchemaDto.setApplicationId(String.valueOf(RANDOM.nextInt())); logSchemaDto.setId(String.valueOf(RANDOM.nextInt())); logSchemaDto.setCreatedTime(System.currentTimeMillis()); if (includeServerProfile) { BaseSchemaInfo schemaInfo = new BaseSchemaInfo(Integer.toString(RANDOM.nextInt()), this.getResourceAsString(SERVER_PROFILE_SCHEMA_FILE)); String body = this.getResourceAsString(SERVER_PROFILE_CONTENT_FILE); logEventPack.setServerProfile(new BaseProfileInfo(schemaInfo, body)); } logEventPack.setLogSchema(new LogSchema(logSchemaDto, LogData.getClassSchema().toString())); return logEventPack; } private void initLogAppender(boolean addServerField) throws IOException { endpointKeyHash = UUID.randomUUID().toString(); profileDto = new EndpointProfileDataDto("1", endpointKeyHash, 1, "", 1, ""); appToken = String.valueOf(RANDOM.nextInt(Integer.MAX_VALUE)); appenderDto = new LogAppenderDto(); appenderDto.setId("Test_id"); appenderDto.setApplicationToken(appToken); appenderDto.setName("Test Name"); appenderDto.setTenantId(String.valueOf(RANDOM.nextInt())); appenderDto.setHeaderStructure(Arrays.asList(LogHeaderStructureDto.values())); appenderDto.setApplicationToken(appToken); header = new RecordHeader(); header.setApplicationToken(appToken); header.setEndpointKeyHash(endpointKeyHash); header.setHeaderVersion(1); header.setTimestamp(System.currentTimeMillis()); CassandraServer server = new CassandraServer("127.0.0.1", 9142); configuration = new CassandraConfig(); configuration.setCassandraBatchType(CassandraBatchType.UNLOGGED); configuration.setKeySpace(KEY_SPACE_NAME); configuration.setTableNamePattern("logs_$app_token_$config_hash"); configuration.setCassandraExecuteRequestType(CassandraExecuteRequestType.ASYNC); configuration.setCassandraServers(Arrays.asList(server)); configuration.setCallbackThreadPoolSize(3); configuration.setExecutorThreadPoolSize(3); List<ColumnMappingElement> columnMapping = new ArrayList<ColumnMappingElement>(); columnMapping.add(new ColumnMappingElement(ColumnMappingElementType.HEADER_FIELD, "endpointKeyHash", "endpointKeyHash", ColumnType.TEXT, true, false)); columnMapping.add(new ColumnMappingElement(ColumnMappingElementType.EVENT_JSON, "", "event_json", ColumnType.TEXT, false, false)); columnMapping.add(new ColumnMappingElement(ColumnMappingElementType.UUID, "", "binid", ColumnType.UUID, false, true)); if (addServerField) { // Do NOT change the column name of the following element! columnMapping.add(new ColumnMappingElement(ColumnMappingElementType.SERVER_FIELD, "", "server_field", ColumnType.TEXT, false, false)); } configuration.setColumnMapping(columnMapping); List<ClusteringElement> clusteringMapping = new ArrayList<ClusteringElement>(); clusteringMapping.add(new ClusteringElement("binid", OrderType.DESC)); configuration.setClusteringMapping(clusteringMapping); AvroByteArrayConverter<CassandraConfig> converter = new AvroByteArrayConverter<>(CassandraConfig.class); byte[] rawConfiguration = converter.toByteArray(configuration); appenderDto.setRawConfiguration(rawConfiguration); logAppender = new CassandraLogAppender(); logAppender.init(appenderDto); logAppender.setApplicationToken(appToken); } protected String getResourceAsString(String path) throws IOException { URL url = Thread.currentThread().getContextClassLoader().getResource(path); File file = new File(url.getPath()); String result; BufferedReader br = new BufferedReader(new FileReader(file)); try { StringBuilder sb = new StringBuilder(); String line = br.readLine(); while (line != null) { sb.append(line); sb.append(System.lineSeparator()); line = br.readLine(); } result = sb.toString(); } finally { br.close(); } return result; } class DeliveryCallback implements LogDeliveryCallback { private AtomicInteger successCount = new AtomicInteger(); @Override public void onSuccess() { successCount.incrementAndGet(); } @Override public void onInternalError() { } @Override public void onConnectionError() { } @Override public void onRemoteError() { } public int getSuccessCount() { return successCount.get(); } } }