/*
* *****************************************************************************
* Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
* *****************************************************************************
*
* 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.pentaho.kettle.engines.storm.bolt;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.junit.Before;
import org.junit.Test;
import org.pentaho.kettle.engines.storm.KettleControlSignal;
import org.pentaho.kettle.engines.storm.Notifier;
import org.pentaho.kettle.engines.storm.signal.KettleSignal;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.tuple.Tuple;
public class KettleControlBoltTest {
private static final String TRANS_NAME = "transformation 1";
private static final String STEP_1 = "step 1";
private static final String STEP_2 = "step 2";
private static final int TASK_ID_1 = 1723;
private static final int TASK_ID_2 = 18;
private static final KettleSignal STEP_1_COMPLETE = new KettleSignal(STEP_1,
TASK_ID_1, KettleControlSignal.COMPLETE);
private static final KettleSignal STEP_2_COMPLETE = new KettleSignal(STEP_2,
TASK_ID_2, KettleControlSignal.COMPLETE);
private IMocksControl control;
private Notifier notifier;
private TopologyContext context;
private OutputCollector collector;
@SuppressWarnings("rawtypes")
@Before
public void init() {
control = EasyMock.createControl();
notifier = control.createMock(Notifier.class);
notifier.init(EasyMock.<Map> anyObject());
EasyMock.expectLastCall().anyTimes();
collector = control.createMock(OutputCollector.class);
context = control.createMock(TopologyContext.class);
}
@Test(expected = IllegalArgumentException.class)
public void construct() {
new KettleControlBolt(null, notifier, Collections.singleton("step"));
}
@Test(expected = IllegalStateException.class)
public void prepare_no_tasks_for_leaf_step_null() {
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Collections.singleton(STEP_1));
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(null);
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
}
@Test(expected = IllegalStateException.class)
public void prepare_no_tasks_for_leaf_step_empty() {
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Collections.singleton(STEP_1));
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(
Collections.<Integer> emptyList());
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
}
/**
* Create a tuple for the given signal.
*
* @param signal
* Signal to emit as a single value tuple.
* @return The tuple.
*/
private Tuple createTupleForSignal(KettleSignal signal) {
Tuple input = control.createMock(Tuple.class);
EasyMock.expect(input.getValue(0)).andReturn(signal).anyTimes();
return input;
}
/**
* Verify the last task to complete triggers the notifier.
*/
@Test
public void execute_last_task() throws Exception {
// Test set up
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Collections.singleton(STEP_1));
List<Integer> taskIds = Collections.singletonList(TASK_ID_1);
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(taskIds);
Tuple step1Complete = createTupleForSignal(STEP_1_COMPLETE);
// Expect that our notifier is notified after receiving a complete signal
// for our one and only leaf node
notifier.notify(TRANS_NAME, KettleControlSignal.COMPLETE);
EasyMock.expectLastCall();
// The tuple should be acknowledged
collector.ack(step1Complete);
EasyMock.expectLastCall();
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
bolt.execute(step1Complete);
control.verify();
}
/**
* Verify notifications are not sent if there are pending steps.
*/
@Test
public void execute_not_last_step() throws Exception {
// Test set up
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Sets.newHashSet(STEP_1, STEP_2));
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(
Collections.singletonList(TASK_ID_1));
EasyMock.expect(context.getComponentTasks(STEP_2)).andReturn(
Collections.singletonList(TASK_ID_2));
Tuple step1Complete = createTupleForSignal(STEP_1_COMPLETE);
// The tuple should be acknowledged
collector.ack(step1Complete);
EasyMock.expectLastCall();
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
bolt.execute(step1Complete);
control.verify();
}
/**
* Verify notifications are sent after all leaf steps are complete.
*/
@Test
public void execute_multiple_steps() throws Exception {
// Test set up
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Sets.newHashSet(STEP_1, STEP_2));
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(
Collections.singletonList(TASK_ID_1));
EasyMock.expect(context.getComponentTasks(STEP_2)).andReturn(
Collections.singletonList(TASK_ID_2));
Tuple step1Complete = createTupleForSignal(STEP_1_COMPLETE);
Tuple step2Complete = createTupleForSignal(STEP_2_COMPLETE);
// The tuples should be acknowledged
collector.ack(step1Complete);
EasyMock.expectLastCall();
collector.ack(step2Complete);
EasyMock.expectLastCall();
// Expect that our notifier is notified after receiving a complete signal
// for our one and only leaf node
notifier.notify(TRANS_NAME, KettleControlSignal.COMPLETE);
EasyMock.expectLastCall();
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
bolt.execute(step1Complete);
bolt.execute(step2Complete);
control.verify();
}
/**
* Verify notifications are sent after all copies of the leaf steps have
* completed.
*/
@Test
public void execute_single_leaf_step_with_multiple_copies() throws Exception {
// Test set up
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Sets.newHashSet(STEP_1));
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(
Lists.newArrayList(TASK_ID_1, TASK_ID_2));
Tuple task1Complete = createTupleForSignal(new KettleSignal(STEP_1,
TASK_ID_1, KettleControlSignal.COMPLETE));
Tuple task2Complete = createTupleForSignal(new KettleSignal(STEP_1,
TASK_ID_2, KettleControlSignal.COMPLETE));
// The tuples should be acknowledged
collector.ack(task1Complete);
EasyMock.expectLastCall();
collector.ack(task2Complete);
EasyMock.expectLastCall();
// Expect that our notifier is notified after receiving a complete signal
// for our one and only leaf node
notifier.notify(TRANS_NAME, KettleControlSignal.COMPLETE);
EasyMock.expectLastCall();
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
bolt.execute(task1Complete);
bolt.execute(task2Complete);
control.verify();
}
/**
* Verify receiving a signal for a non-leaf step is a failure case.
*/
@Test
public void execute_unexpected_signal() throws Exception {
// Test set up
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Sets.newHashSet(STEP_1));
EasyMock.expect(context.getComponentTasks(STEP_1)).andReturn(
Collections.singletonList(TASK_ID_1));
Tuple unexpectedSignalTuple = createTupleForSignal(new KettleSignal(
"unknown step", 1, KettleControlSignal.COMPLETE));
// The tuple should be failed since we're not expected it.
collector.fail(unexpectedSignalTuple);
EasyMock.expectLastCall();
control.replay();
bolt.prepare(Collections.emptyMap(), context, collector);
bolt.execute(unexpectedSignalTuple);
control.verify();
}
@Test
public void cleanup() {
KettleControlBolt bolt = new KettleControlBolt(TRANS_NAME, notifier,
Collections.singleton(STEP_1));
notifier.cleanup();
EasyMock.expectLastCall();
control.replay();
bolt.cleanup();
control.verify();
}
}