/** * Copyright 2015 StreamSets Inc. * * Licensed under 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 com.streamsets.datacollector.execution.preview; import com.codahale.metrics.MetricRegistry; import com.google.common.collect.ImmutableList; import com.streamsets.datacollector.config.PipelineConfiguration; import com.streamsets.datacollector.execution.PreviewOutput; import com.streamsets.datacollector.execution.PreviewStatus; import com.streamsets.datacollector.execution.Previewer; import com.streamsets.datacollector.execution.PreviewerListener; import com.streamsets.datacollector.execution.RawPreview; import com.streamsets.datacollector.execution.preview.sync.SyncPreviewer; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.main.RuntimeModule; import com.streamsets.datacollector.main.StandaloneRuntimeInfo; import com.streamsets.datacollector.record.RecordImpl; import com.streamsets.datacollector.runner.MockStages; import com.streamsets.datacollector.runner.StageOutput; import com.streamsets.datacollector.stagelibrary.StageLibraryTask; import com.streamsets.datacollector.store.PipelineStoreException; import com.streamsets.datacollector.store.PipelineStoreTask; import com.streamsets.datacollector.util.Configuration; import com.streamsets.datacollector.util.ContainerError; import com.streamsets.datacollector.util.PipelineException; import com.streamsets.pipeline.api.Batch; import com.streamsets.pipeline.api.BatchContext; import com.streamsets.pipeline.api.BatchMaker; import com.streamsets.pipeline.api.ErrorCode; import com.streamsets.pipeline.api.Field; import com.streamsets.pipeline.api.Record; import com.streamsets.pipeline.api.Source; import com.streamsets.pipeline.api.StageException; import com.streamsets.pipeline.api.base.BasePushSource; import com.streamsets.pipeline.api.base.BaseSource; import com.streamsets.pipeline.api.base.BaseTarget; import com.streamsets.pipeline.api.base.SingleLaneRecordProcessor; import dagger.Module; import dagger.ObjectGraph; import dagger.Provides; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import javax.inject.Singleton; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; public abstract class TestPreviewer { protected static final String ID = "myId"; protected static final String NAME = "myPipeline"; protected static final String REV = "0"; protected RuntimeInfo runtimeInfo; protected PreviewerListener previewerListener; protected Configuration configuration; protected StageLibraryTask stageLibrary; protected PipelineStoreTask pipelineStore; protected ObjectGraph objectGraph; @Module(injects = {RuntimeInfo.class, Configuration.class, StageLibraryTask.class, PipelineStoreTask.class, SyncPreviewer.class}, library = true) static class TestPreviewModule { @Provides @Singleton public RuntimeInfo providesRuntimeInfo() { return new StandaloneRuntimeInfo(RuntimeModule.SDC_PROPERTY_PREFIX, new MetricRegistry(), Arrays.asList(TestPreviewer.class.getClassLoader())); } @Provides @Singleton public Configuration provideConfiguration() { Configuration configuration = new Configuration(); return configuration; } @Provides @Singleton public PipelineStoreTask providePipelineStoreTask() { return Mockito.mock(PipelineStoreTask.class); } @Provides @Singleton public StageLibraryTask provideStageLibraryTask() { return MockStages.createStageLibrary(new URLClassLoader(new URL[0])); } } //Mock Error Code implementation enum MockErrorCode implements ErrorCode { MOCK_0000 ; @Override public String getCode() { return "MOCK_0000"; } @Override public String getMessage() { return "Mock Error"; } } static class RecordingPreviewListener implements PreviewerListener { private List<PreviewStatus> previewStatuses; public RecordingPreviewListener() { this.previewStatuses = new ArrayList<>(); } @Override public void statusChange(String id, PreviewStatus status) { previewStatuses.add(status); } @Override public void outputRetrieved(String id) { } public List<PreviewStatus> getPreviewStatuses() { return previewStatuses; } } @Before public void setUp() throws PipelineStoreException { MockStages.resetStageCaptures(); previewerListener = new RecordingPreviewListener(); objectGraph = ObjectGraph.create(TestPreviewModule.class); runtimeInfo = objectGraph.get(RuntimeInfo.class); configuration = objectGraph.get(Configuration.class); stageLibrary = objectGraph.get(StageLibraryTask.class); pipelineStore = objectGraph.get(PipelineStoreTask.class); } protected abstract Previewer createPreviewer(); @Test public void testValidationConfigsPass() throws Throwable { //Source validateConfigs method is overridden to create a config issue with error code CONTAINER_0000 MockStages.setSourceCapture(new BaseSource() { @Override public List<ConfigIssue> init(Info info, Source.Context context) { return Collections.emptyList(); } @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); Previewer previewer = createPreviewer(); previewer.validateConfigs(5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.VALID.name(), previewer.getStatus().name()); Assert.assertEquals(0, previewer.getOutput().getIssues().getIssueCount()); Assert.assertEquals(PreviewStatus.VALID.name(), previewer.getOutput().getStatus().name()); Assert.assertNull(previewer.getOutput().getOutput()); List<PreviewStatus> previewStatuses = ((RecordingPreviewListener) previewerListener).getPreviewStatuses(); Assert.assertEquals(2, previewStatuses.size()); Assert.assertEquals(PreviewStatus.VALIDATING.name(), previewStatuses.get(0).name()); Assert.assertEquals(PreviewStatus.VALID.name(), previewStatuses.get(1).name()); } @Test public void testValidationConfigsFail() throws Throwable { //Source validateConfigs method is overridden to create a config issue with error code CONTAINER_0000 MockStages.setSourceCapture(new BaseSource() { @Override public List<ConfigIssue> init(Info info, Source.Context context) { return Arrays.asList(context.createConfigIssue(null, null, ContainerError.CONTAINER_0000)); } @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); Previewer previewer = createPreviewer(); previewer.validateConfigs(5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.INVALID.name(), previewer.getStatus().name()); Assert.assertEquals(1, previewer.getOutput().getIssues().getIssueCount()); Assert.assertEquals(PreviewStatus.INVALID.name(), previewer.getOutput().getStatus().name()); Assert.assertNull(previewer.getOutput().getOutput()); } @Test public void testValidationConfigsException() throws Throwable { //Source validateConfigs method is overridden to create a config issue with error code CONTAINER_0000 MockStages.setSourceCapture(new BaseSource() { @Override public List<ConfigIssue> init(Info info, Source.Context context) { return ImmutableList.of(context.createConfigIssue(null, null, MockErrorCode.MOCK_0000)); } @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); Previewer previewer = createPreviewer(); previewer.validateConfigs(5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.INVALID, previewer.getStatus()); Assert.assertTrue(previewer.getOutput().getIssues().getIssueCount() > 0); } @Test public void testPreviewRun() throws Throwable { Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); MockStages.setSourceCapture(new BaseSource() { @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { Record record = getContext().createRecord("x"); record.set(Field.create(1)); batchMaker.addRecord(record); return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); //create Sync Previewer Previewer previewer = createPreviewer(); //check id, name, revision Assert.assertEquals(ID, previewer.getId()); Assert.assertEquals(NAME, previewer.getName()); Assert.assertEquals(REV, previewer.getRev()); Assert.assertEquals(PreviewStatus.CREATED, previewer.getStatus()); //start preview previewer.start(1, 10, false, null, new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); //when sync previewer returns from start, the preview should be finished Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); //stop should be a no-op previewer.stop(); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); //check the output PreviewOutput previewOutput = previewer.getOutput(); List<StageOutput> output = previewOutput.getOutput().get(0); Assert.assertEquals(1, output.get(0).getOutput().get("s").get(0).get().getValue()); Assert.assertEquals(2, output.get(1).getOutput().get("p").get(0).get().getValue()); List<PreviewStatus> previewStatuses = ((RecordingPreviewListener) previewerListener).getPreviewStatuses(); Assert.assertEquals(2, previewStatuses.size()); Assert.assertEquals(PreviewStatus.RUNNING.name(), previewStatuses.get(0).name()); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewStatuses.get(1).name()); } @Test public void testPreviewRunPushSource() throws Throwable { Mockito .when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())) .thenReturn(MockStages.createPipelineConfigurationPushSourceTarget()); MockStages.setPushSourceCapture(new BasePushSource() { @Override public int getNumberOfThreads() { return 1; } @Override public void produce(Map<String, String> lastOffsets, int maxBatchSize) throws StageException { BatchContext batchContext = getContext().startBatch(); Record record = getContext().createRecord("x"); record.set(Field.create(1)); batchContext.getBatchMaker().addRecord(record); getContext().processBatch(batchContext); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); //create Sync Previewer Previewer previewer = createPreviewer(); //check id, name, revision Assert.assertEquals(ID, previewer.getId()); Assert.assertEquals(NAME, previewer.getName()); Assert.assertEquals(REV, previewer.getRev()); Assert.assertEquals(PreviewStatus.CREATED, previewer.getStatus()); //start preview previewer.start(1, 10, false, null, new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); //when sync previewer returns from start, the preview should be finished Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); //stop should be a no-op previewer.stop(); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); //check the output PreviewOutput previewOutput = previewer.getOutput(); List<StageOutput> output = previewOutput.getOutput().get(0); Assert.assertEquals(1, output.get(0).getOutput().get("a").get(0).get().getValue()); List<PreviewStatus> previewStatuses = ((RecordingPreviewListener) previewerListener).getPreviewStatuses(); Assert.assertEquals(2, previewStatuses.size()); Assert.assertEquals(PreviewStatus.RUNNING.name(), previewStatuses.get(0).name()); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewStatuses.get(1).name()); } @Test public void testPreviewPipelineBuilderWithLastStage() throws Throwable { Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); MockStages.setSourceCapture(new BaseSource() { @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { Record record = getContext().createRecord("x"); record.set(Field.create(1)); batchMaker.addRecord(record); return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); //batchMaker.addRecord(record); } }); Previewer previewer = createPreviewer(); previewer.start(1, 10, true, "p", new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); PreviewOutput previewOutput = previewer.getOutput(); List<StageOutput> output = previewOutput.getOutput().get(0); Assert.assertEquals(1, output.size()); //complex graph Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationComplexSourceProcessorTarget()); previewer = createPreviewer(); previewer.start(1, 10, true, "p1", new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); previewOutput = previewer.getOutput(); output = previewOutput.getOutput().get(0); Assert.assertEquals(1, output.size()); Assert.assertEquals(1, output.get(0).getOutput().get("s").get(0).get().getValue()); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationComplexSourceProcessorTarget()); previewer = createPreviewer(); previewer.start(1, 10, true, "p5", new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); previewOutput = previewer.getOutput(); output = previewOutput.getOutput().get(0); Assert.assertEquals(2, output.size()); Assert.assertEquals(1, output.get(0).getOutput().get("s").get(0).get().getValue()); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationComplexSourceProcessorTarget()); previewer = createPreviewer(); previewer.start(1, 10, true, "p6", new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); previewOutput = previewer.getOutput(); output = previewOutput.getOutput().get(0); Assert.assertEquals(3, output.size()); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationComplexSourceProcessorTarget()); previewer = createPreviewer(); previewer.start(1, 10, true, "t", new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); previewOutput = previewer.getOutput(); output = previewOutput.getOutput().get(0); Assert.assertEquals(7, output.size()); } @Test public void testPreviewRunOverride() throws Throwable { MockStages.setSourceCapture(new BaseSource() { @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { Record record = getContext().createRecord("x"); record.set(Field.create(1)); batchMaker.addRecord(record); return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { int currentValue = record.get().getValueAsInteger(); record.set(Field.create(currentValue * 2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); PipelineConfiguration pipelineConf = MockStages.createPipelineConfigurationSourceProcessorTarget(); Previewer previewer = createPreviewer(); previewer.start(1, 10, true, null, new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); PreviewOutput previewOutput = previewer.getOutput(); List<StageOutput> output = previewOutput.getOutput().get(0); Assert.assertEquals(1, output.get(0).getOutput().get("s").get(0).get().getValue()); Assert.assertEquals(2, output.get(1).getOutput().get("p").get(0).get().getValue()); StageOutput sourceOutput = output.get(0); Assert.assertEquals("s", sourceOutput.getInstanceName()); Record modRecord = new RecordImpl("i", "source", null, null); modRecord.set(Field.create(10)); //modifying the source output sourceOutput.getOutput().get(pipelineConf.getStages().get(0).getOutputLanes().get(0)).set(0, modRecord); previewer.start(1, 10, true, null, Arrays.asList(sourceOutput), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.FINISHED.name(), previewer.getStatus().name()); previewOutput = previewer.getOutput(); output = previewOutput.getOutput().get(0); Assert.assertEquals(10, output.get(0).getOutput().get("s").get(0).get().getValue()); Assert.assertEquals(20, output.get(1).getOutput().get("p").get(0).get().getValue()); } @Test public void testPreviewException() throws Throwable { //Source validateConfigs method is overridden to create a config issue with error code CONTAINER_0000 MockStages.setSourceCapture(new BaseSource() { @Override public List<ConfigIssue> init(Info info, Source.Context context) { throw new RuntimeException(); } @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); Previewer previewer = createPreviewer(); previewer.start(1, 10, true, null, new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.INVALID, previewer.getStatus()); Assert.assertTrue(previewer.getOutput().getIssues().getIssueCount() > 0); } @Test public void testPreviewFailValidation() throws Throwable { //Source validateConfigs method is overridden to create a config issue with error code CONTAINER_0000 MockStages.setSourceCapture(new BaseSource() { @Override public List<ConfigIssue> init(Info info, Source.Context context) { return Arrays.asList(context.createConfigIssue(null, null, ContainerError.CONTAINER_0000)); } @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { return "1"; } }); MockStages.setProcessorCapture(new SingleLaneRecordProcessor() { @Override protected void process(Record record, SingleLaneBatchMaker batchMaker) throws StageException { record.set(Field.create(2)); batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); Previewer previewer = createPreviewer(); previewer.start(1, 10, true, null, new ArrayList<StageOutput>(), 5000); previewer.waitForCompletion(5000); Assert.assertEquals(PreviewStatus.INVALID.name(), previewer.getStatus().name()); Assert.assertEquals(1, previewer.getOutput().getIssues().getIssueCount()); Assert.assertEquals(PreviewStatus.INVALID.name(), previewer.getOutput().getStatus().name()); Assert.assertNull(previewer.getOutput().getOutput()); } @Test public void testRawSourcePreview() throws PipelineException, IOException { Mockito.when(pipelineStore.load(Mockito.anyString(), Mockito.anyString())).thenReturn(MockStages.createPipelineConfigurationSourceProcessorTarget()); Previewer previewer = createPreviewer(); MultivaluedMap<String, String> map = new MultivaluedHashMap<>(); map.putSingle("brokerHost", "localhost"); map.putSingle("brokerPort", "9001"); RawPreview rawSource = previewer.getRawSource(100, map); Assert.assertEquals("*/*", rawSource.getMimeType()); Assert.assertEquals("localhost:9001", rawSource.getPreviewData()); } }