/*
* Copyright © 2014-2015 Cask Data, 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 co.cask.cdap.client;
import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.api.data.format.FormatSpecification;
import co.cask.cdap.api.flow.flowlet.StreamEvent;
import co.cask.cdap.client.common.ClientTestBase;
import co.cask.cdap.common.BadRequestException;
import co.cask.cdap.common.StreamNotFoundException;
import co.cask.cdap.common.UnauthenticatedException;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.NamespaceMeta;
import co.cask.cdap.proto.StreamProperties;
import co.cask.cdap.proto.ViewSpecification;
import co.cask.cdap.test.XSlowTests;
import com.google.common.base.Charsets;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Test for {@link StreamClient}.
*/
@Category(XSlowTests.class)
public class StreamClientTestRun extends ClientTestBase {
private static final Logger LOG = LoggerFactory.getLogger(StreamClientTestRun.class);
private static final Id.Namespace namespaceId = Id.Namespace.from("myspace");
private NamespaceClient namespaceClient;
private StreamClient streamClient;
@Before
public void setUp() throws Throwable {
super.setUp();
namespaceClient = new NamespaceClient(clientConfig);
namespaceClient.create(new NamespaceMeta.Builder().setName(namespaceId).build());
streamClient = new StreamClient(clientConfig);
}
@Test
public void testAll() throws Exception {
Id.Stream streamId = Id.Stream.from(namespaceId, "testAll");
LOG.info("Getting stream list");
int baseStreamCount = streamClient.list(namespaceId).size();
Assert.assertEquals(baseStreamCount, streamClient.list(namespaceId).size());
LOG.info("Creating stream");
streamClient.create(streamId);
LOG.info("Checking stream list");
Assert.assertEquals(baseStreamCount + 1, streamClient.list(namespaceId).size());
StreamProperties config = streamClient.getConfig(streamId);
Assert.assertNotNull(config);
}
@Test
public void testDescription() throws Exception {
String description = "Good Stream";
Long ttl = 10L;
Integer notifMB = 300;
Id.Stream streamId = Id.Stream.from(namespaceId, "testDesc");
StreamProperties properties = new StreamProperties(ttl, null, notifMB, description);
streamClient.create(streamId, properties);
StreamProperties actual = streamClient.getConfig(streamId);
Assert.assertEquals(description, actual.getDescription());
Assert.assertEquals(ttl, actual.getTTL());
Assert.assertEquals(notifMB, actual.getNotificationThresholdMB());
description = "New Description";
streamClient.setDescription(streamId, description);
actual = streamClient.getConfig(streamId);
Assert.assertEquals(description, actual.getDescription());
Assert.assertEquals(ttl, actual.getTTL());
Assert.assertEquals(notifMB, actual.getNotificationThresholdMB());
}
/**
* Tests for the get events call
*/
@Test
public void testStreamEvents() throws IOException, BadRequestException,
StreamNotFoundException, UnauthenticatedException {
Id.Stream streamId = Id.Stream.from(namespaceId, "testEvents");
streamClient.create(streamId);
// Send 5000 events
int eventCount = 5000;
for (int i = 0; i < eventCount; i++) {
streamClient.sendEvent(streamId, "Testing " + i);
}
// Read all events
List<StreamEvent> events = streamClient.getEvents(streamId, 0, Long.MAX_VALUE,
Integer.MAX_VALUE, Lists.<StreamEvent>newArrayList());
Assert.assertEquals(eventCount, events.size());
for (int i = 0; i < eventCount; i++) {
Assert.assertEquals("Testing " + i, Bytes.toString(events.get(i).getBody()));
}
// Read first 5 only
events.clear();
streamClient.getEvents(streamId, 0, Long.MAX_VALUE, 5, events);
Assert.assertEquals(5, events.size());
// Use the 2nd and the 3rd events time as start and end time respectively
long startTime = events.get(1).getTimestamp();
long endTime = events.get(2).getTimestamp() + 1;
events.clear();
streamClient.getEvents(streamId, startTime, endTime, Integer.MAX_VALUE, events);
// At least read the 2nd and the 3rd event. It might read more than 2 events
// if events are written within the same timestamp.
Assert.assertTrue(events.size() >= 2);
int i = 1;
for (StreamEvent event : events) {
Assert.assertEquals("Testing " + i, Charsets.UTF_8.decode(events.get(i - 1).getBody()).toString());
Assert.assertTrue(event.getTimestamp() >= startTime);
Assert.assertTrue(event.getTimestamp() < endTime);
i++;
}
}
/**
* Tests for async write to stream.
*/
@Test
public void testAsyncWrite() throws Exception {
Id.Stream streamId = Id.Stream.from(namespaceId, "testAsync");
streamClient.create(streamId);
// Send 10 async writes
int msgCount = 10;
for (int i = 0; i < msgCount; i++) {
streamClient.asyncSendEvent(streamId, "Testing " + i);
}
// Reads them back to verify. Needs to do it multiple times as the writes happens async.
List<StreamEvent> events = Lists.newArrayList();
Stopwatch stopwatch = new Stopwatch();
stopwatch.start();
while (events.size() != msgCount && stopwatch.elapsedTime(TimeUnit.SECONDS) < 10L) {
events.clear();
streamClient.getEvents(streamId, 0, Long.MAX_VALUE, msgCount, events);
}
Assert.assertEquals(msgCount, events.size());
long lastTimestamp = 0L;
for (int i = 0; i < msgCount; i++) {
Assert.assertEquals("Testing " + i, Charsets.UTF_8.decode(events.get(i).getBody()).toString());
lastTimestamp = events.get(i).getTimestamp();
}
// No more events
stopwatch = new Stopwatch();
stopwatch.start();
events.clear();
while (events.isEmpty() && stopwatch.elapsedTime(TimeUnit.SECONDS) < 1L) {
events.clear();
streamClient.getEvents(streamId, lastTimestamp + 1, Long.MAX_VALUE, msgCount, events);
}
Assert.assertTrue(events.isEmpty());
}
@Test
public void testSendSmallFile() throws Exception {
testSendFile("Short message", 50);
}
@Test
public void testSendLargeFile() throws Exception {
// Each event is ~ 4K in size
String messagePrefix = Strings.repeat("0123456789", 410);
// In stream, by default "large" file is > 1MB in size, hence sending 300 events
testSendFile(messagePrefix, 300);
}
@Test
public void testDelete() throws Exception {
Id.Stream streamId = Id.Stream.from(namespaceId, "testDelete");
streamClient.create(streamId);
// Send an event and get it back
String msg = "Test Delete";
streamClient.sendEvent(streamId, msg);
List<StreamEvent> events = Lists.newArrayList();
streamClient.getEvents(streamId, 0, Long.MAX_VALUE, Integer.MAX_VALUE, events);
Assert.assertEquals(1, events.size());
Assert.assertEquals(msg, Charsets.UTF_8.decode(events.get(0).getBody()).toString());
// Delete the stream
streamClient.delete(streamId);
// Try to get info, it should throw a StreamNotFoundException
try {
streamClient.getConfig(streamId);
Assert.fail();
} catch (StreamNotFoundException e) {
// Expected
}
// Try to get events, it should throw a StreamNotFoundException
try {
streamClient.getEvents(streamId, 0, Long.MAX_VALUE, Integer.MAX_VALUE, events);
Assert.fail();
} catch (StreamNotFoundException e) {
// Expected
}
// Create the stream again, it should returns empty events
streamClient.create(streamId);
events.clear();
streamClient.getEvents(streamId, 0, Long.MAX_VALUE, Integer.MAX_VALUE, events);
Assert.assertTrue(events.isEmpty());
}
@Test
public void testStreamDeleteAfterCreatingView() throws Exception {
Id.Stream testStream = Id.Stream.from(Id.Namespace.DEFAULT, "testStream");
streamClient.create(testStream);
// should throw StreamNotFoundException if the stream has not been successfully created in the previous step
streamClient.getConfig(testStream);
StreamViewClient streamViewClient = new StreamViewClient(clientConfig);
Id.Stream.View testView = Id.Stream.View.from(testStream, "testView");
ViewSpecification testViewSpec = new ViewSpecification(new FormatSpecification("csv", null, null));
Assert.assertTrue(streamViewClient.createOrUpdate(testView, testViewSpec));
// test stream delete
streamClient.delete(testStream);
// recreate the stream and the view
streamClient.create(testStream);
// should throw StreamNotFoundException if the stream has not been successfully created in the previous step
streamClient.getConfig(testStream);
Assert.assertTrue(streamViewClient.createOrUpdate(testView, testViewSpec));
// test that namespace deletion succeeds
namespaceClient.delete(Id.Namespace.DEFAULT);
}
private void testSendFile(String msgPrefix, int msgCount) throws Exception {
Id.Stream streamId = Id.Stream.from(namespaceId, "testSendFile");
streamClient.create(streamId);
// Generate msgCount lines of events
File file = TMP_FOLDER.newFile();
try (BufferedWriter writer = Files.newWriter(file, Charsets.UTF_8)) {
for (int i = 0; i < msgCount; i++) {
writer.write(msgPrefix + i);
writer.newLine();
}
}
streamClient.sendFile(streamId, "text/plain", file);
// Reads the msgCount events back
List<StreamEvent> events = Lists.newArrayList();
streamClient.getEvents(streamId, 0, Long.MAX_VALUE, Integer.MAX_VALUE, events);
Assert.assertEquals(msgCount, events.size());
for (int i = 0; i < msgCount; i++) {
StreamEvent event = events.get(i);
Assert.assertEquals(msgPrefix + i, Bytes.toString(event.getBody()));
Assert.assertEquals("text/plain", event.getHeaders().get("content.type"));
}
}
@After
public void tearDown() throws Exception {
namespaceClient.delete(namespaceId);
}
}