/** * 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.util; import com.codahale.metrics.MetricRegistry; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.ServerSetup; import com.streamsets.datacollector.config.DataRuleDefinition; import com.streamsets.datacollector.config.DriftRuleDefinition; import com.streamsets.datacollector.config.MetricsRuleDefinition; import com.streamsets.datacollector.config.PipelineConfiguration; import com.streamsets.datacollector.config.RuleDefinitions; import com.streamsets.datacollector.config.ThresholdType; import com.streamsets.datacollector.creation.RuleDefinitionsConfigBean; import com.streamsets.datacollector.email.EmailSender; import com.streamsets.datacollector.execution.EventListenerManager; import com.streamsets.datacollector.execution.PipelineStateStore; import com.streamsets.datacollector.execution.Previewer; import com.streamsets.datacollector.execution.PreviewerListener; import com.streamsets.datacollector.execution.Runner; import com.streamsets.datacollector.execution.SnapshotStore; import com.streamsets.datacollector.execution.alerts.AlertManager; import com.streamsets.datacollector.execution.manager.PreviewerProvider; import com.streamsets.datacollector.execution.manager.RunnerProvider; import com.streamsets.datacollector.execution.manager.standalone.StandaloneAndClusterPipelineManager; import com.streamsets.datacollector.execution.runner.common.AsyncRunner; import com.streamsets.datacollector.execution.runner.common.DataObserverRunnable; import com.streamsets.datacollector.execution.runner.common.MetricObserverRunnable; import com.streamsets.datacollector.execution.runner.common.MetricsObserverRunner; import com.streamsets.datacollector.execution.runner.common.ProductionObserver; import com.streamsets.datacollector.execution.runner.common.ProductionPipelineRunner; import com.streamsets.datacollector.execution.runner.common.RulesConfigLoader; import com.streamsets.datacollector.execution.runner.common.ThreadHealthReporter; import com.streamsets.datacollector.execution.runner.standalone.StandaloneRunner; import com.streamsets.datacollector.execution.snapshot.file.FileSnapshotStore; import com.streamsets.datacollector.execution.store.CachePipelineStateStore; import com.streamsets.datacollector.execution.store.FilePipelineStateStore; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.main.RuntimeModule; import com.streamsets.datacollector.main.StandaloneRuntimeInfo; import com.streamsets.datacollector.runner.MockStages; import com.streamsets.datacollector.runner.Observer; import com.streamsets.datacollector.runner.PipelineRunner; import com.streamsets.datacollector.runner.SourceOffsetTracker; import com.streamsets.datacollector.runner.production.ProductionSourceOffsetTracker; import com.streamsets.datacollector.runner.production.RulesConfigLoaderRunnable; import com.streamsets.datacollector.stagelibrary.StageLibraryTask; import com.streamsets.datacollector.store.AclStoreTask; import com.streamsets.datacollector.store.PipelineStoreException; import com.streamsets.datacollector.store.PipelineStoreTask; import com.streamsets.datacollector.store.impl.FileAclStoreTask; import com.streamsets.datacollector.store.impl.FilePipelineStoreTask; import com.streamsets.pipeline.api.Batch; import com.streamsets.pipeline.api.BatchMaker; import com.streamsets.pipeline.api.Config; import com.streamsets.pipeline.api.ExecutionMode; 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.BaseSource; import com.streamsets.pipeline.api.base.BaseTarget; import com.streamsets.pipeline.api.base.SingleLaneProcessor; import com.streamsets.pipeline.api.base.SingleLaneRecordProcessor; import com.streamsets.pipeline.api.impl.Utils; import com.streamsets.pipeline.lib.executor.SafeScheduledExecutorService; import dagger.Module; import dagger.ObjectGraph; import dagger.Provides; import org.mockito.Mockito; import javax.inject.Named; import javax.inject.Singleton; import java.io.IOException; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; public class TestUtil { public static final String USER = "user"; public static final String MY_PIPELINE = "my pipeline"; public static final String MY_SECOND_PIPELINE = "my second pipeline"; public static final String HIGHER_VERSION_PIPELINE = "higher version pipeline"; public static final String PIPELINE_WITH_EMAIL = "Pipeline with Email"; public static final String PIPELINE_TITLE_WITH_EMAIL = "label"; public static final String PIPELINE_REV = "2.0"; public static final String ZERO_REV = "0"; public volatile static boolean EMPTY_OFFSET = false; public static class SourceOffsetTrackerImpl implements SourceOffsetTracker { private final Map<String, String> offsets; private boolean finished; private long lastBatchTime; public SourceOffsetTrackerImpl(Map<String, String> offsets) { this.offsets = new HashMap<>(offsets); finished = false; } @Override public boolean isFinished() { return finished; } @Override public void commitOffset(String entity, String newOffset) { lastBatchTime = System.currentTimeMillis(); System.out.println(Utils.format("Committing entity({}), offset({}) on time({})", entity, newOffset, lastBatchTime)); if(entity == null) { return; } if(Source.POLL_SOURCE_OFFSET_KEY.equals(entity)) { finished = (newOffset == null); } if(newOffset == null) { offsets.remove(entity); } else { offsets.put(entity, newOffset); } } @Override public Map<String, String> getOffsets() { return offsets; } @Override public long getLastBatchTime() { return lastBatchTime; } } /********************************************/ /********* Pipeline using Mock Stages *******/ /********************************************/ public static void captureMockStages() { MockStages.setSourceCapture(new BaseSource() { private int recordsProducedCounter = 0; @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); recordsProducedCounter++; if (recordsProducedCounter == 1) { recordsProducedCounter = 0; return null; } 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 { } }); } public static void captureStagesForProductionRun() { MockStages.setSourceCapture(new BaseSource() { @Override public String produce(String lastSourceOffset, int maxBatchSize, BatchMaker batchMaker) throws StageException { maxBatchSize = (maxBatchSize > -1) ? maxBatchSize : 10; for (int i = 0; i < maxBatchSize; i++ ) { batchMaker.addRecord(createRecord(lastSourceOffset, i)); } return EMPTY_OFFSET == true ? null: "random"; } private Record createRecord(String lastSourceOffset, int batchOffset) { Record record = getContext().createRecord("random:" + batchOffset); Map<String, Field> map = new HashMap<>(); map.put("name", Field.create(UUID.randomUUID().toString())); map.put("time", Field.create(System.currentTimeMillis())); record.set(Field.create(map)); return record; } }); MockStages.setProcessorCapture(new SingleLaneProcessor() { private Random random; @Override protected List<ConfigIssue> init() { List<ConfigIssue> issues = super.init(); random = new Random(); return issues; } @Override public void process(Batch batch, SingleLaneBatchMaker batchMaker) throws StageException { Iterator<Record> it = batch.getRecords(); while (it.hasNext()) { float action = random.nextFloat(); getContext().toError(it.next(), "Random error"); } getContext().reportError("Random pipeline error"); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); } public static void captureMockStagesLongWait() { 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)); try { Thread.sleep(1000000); } catch (InterruptedException e) { // No-op } batchMaker.addRecord(record); } }); MockStages.setTargetCapture(new BaseTarget() { @Override public void write(Batch batch) throws StageException { } }); } /********************************************/ /*************** Providers for Dagger *******/ /********************************************/ /*************** StageLibrary ***************/ @Module(library = true) public static class TestStageLibraryModule { public TestStageLibraryModule() { } @Provides @Singleton public StageLibraryTask provideStageLibrary() { return MockStages.createStageLibrary(); } } /*************** PipelineStore ***************/ // TODO - Rename TestPipelineStoreModule after multi pipeline support @Module( injects = {PipelineStoreTask.class, Configuration.class}, library = true, includes = {TestRuntimeModule.class, TestStageLibraryModule.class, TestPipelineStateStoreModule.class } ) public static class TestPipelineStoreModuleNew { public TestPipelineStoreModuleNew() { } @Provides @Singleton public PipelineStoreTask providePipelineStore(RuntimeInfo info, StageLibraryTask stageLibraryTask, PipelineStateStore pipelineStateStore) { FilePipelineStoreTask pipelineStoreTask = new FilePipelineStoreTask(info, stageLibraryTask, pipelineStateStore, new LockCache<String>()); pipelineStoreTask.init(); try { //create an invalid pipeline //The if check is needed because the tests restart the pipeline manager. In that case the check prevents //us from trying to create the same pipeline again if(!pipelineStoreTask.hasPipeline("invalid")) { pipelineStoreTask.create(USER, "invalid", "label" ,"invalid its empty", false); PipelineConfiguration pipelineConf = pipelineStoreTask.load("invalid", PIPELINE_REV); PipelineConfiguration mockPipelineConf = MockStages.createPipelineConfigurationSourceTarget(); pipelineConf.setErrorStage(mockPipelineConf.getErrorStage()); pipelineConf.getConfiguration().add(new Config("executionMode", ExecutionMode.STANDALONE.name())); } if (!pipelineStoreTask.hasPipeline(MY_PIPELINE)) { pipelineStoreTask.create(USER, MY_PIPELINE, "label" ,"description", false); PipelineConfiguration pipelineConf = pipelineStoreTask.load(MY_PIPELINE, ZERO_REV); PipelineConfiguration mockPipelineConf = MockStages.createPipelineConfigurationSourceTarget(); pipelineConf.setStages(mockPipelineConf.getStages()); pipelineConf.setErrorStage(mockPipelineConf.getErrorStage()); pipelineConf.setStatsAggregatorStage(mockPipelineConf.getStatsAggregatorStage()); pipelineConf.getConfiguration().add(new Config("executionMode", ExecutionMode.STANDALONE.name())); pipelineConf.getConfiguration().add(new Config("retryAttempts", 3)); pipelineStoreTask.save("admin", MY_PIPELINE, ZERO_REV, "description", pipelineConf); // create a DataRuleDefinition for one of the stages DataRuleDefinition dataRuleDefinition = new DataRuleDefinition("myID", "myLabel", "s", 100, 10, "${record:value(\"/name\") != null}", true, "alertText", ThresholdType.COUNT, "100", 100, true, false, true, System.currentTimeMillis()); List<DataRuleDefinition> dataRuleDefinitions = new ArrayList<>(); dataRuleDefinitions.add(dataRuleDefinition); RuleDefinitions ruleDefinitions = new RuleDefinitions( PipelineStoreTask.RULE_DEFINITIONS_SCHEMA_VERSION, RuleDefinitionsConfigBean.VERSION, Collections.<MetricsRuleDefinition> emptyList(), dataRuleDefinitions, Collections.<DriftRuleDefinition>emptyList(), Collections.<String> emptyList(), UUID.randomUUID(), Collections.emptyList() ); pipelineStoreTask.storeRules(MY_PIPELINE, ZERO_REV, ruleDefinitions); } if(!pipelineStoreTask.hasPipeline(MY_SECOND_PIPELINE)) { pipelineStoreTask.create("user2", MY_SECOND_PIPELINE, "label" ,"description2", false); PipelineConfiguration pipelineConf = pipelineStoreTask.load(MY_SECOND_PIPELINE, ZERO_REV); PipelineConfiguration mockPipelineConf = MockStages.createPipelineConfigurationSourceProcessorTarget(); pipelineConf.setStages(mockPipelineConf.getStages()); pipelineConf.setErrorStage(mockPipelineConf.getErrorStage()); pipelineConf.setStatsAggregatorStage(mockPipelineConf.getStatsAggregatorStage()); pipelineConf.getConfiguration().add(new Config("executionMode", ExecutionMode.STANDALONE.name())); pipelineStoreTask.save("admin2", MY_SECOND_PIPELINE, ZERO_REV, "description" , pipelineConf); } if(!pipelineStoreTask.hasPipeline(HIGHER_VERSION_PIPELINE)) { PipelineConfiguration pipelineConfiguration = pipelineStoreTask.create("user2", HIGHER_VERSION_PIPELINE, "label" ,"description2", false); PipelineConfiguration mockPipelineConf = MockStages.createPipelineConfigurationSourceProcessorTargetHigherVersion(); mockPipelineConf.getConfiguration().add(new Config("executionMode", ExecutionMode.STANDALONE.name())); mockPipelineConf.setUuid(pipelineConfiguration.getUuid()); pipelineStoreTask.save("admin2", HIGHER_VERSION_PIPELINE, ZERO_REV, "description" , mockPipelineConf); } if(!pipelineStoreTask.hasPipeline(PIPELINE_WITH_EMAIL)) { pipelineStoreTask.create("user2", PIPELINE_WITH_EMAIL, "label" ,"description2", false); PipelineConfiguration pipelineConf = pipelineStoreTask.load(PIPELINE_WITH_EMAIL, ZERO_REV); PipelineConfiguration mockPipelineConf = MockStages.createPipelineConfigurationSourceProcessorTarget(); pipelineConf.setStages(mockPipelineConf.getStages()); pipelineConf.setErrorStage(mockPipelineConf.getErrorStage()); pipelineConf.setStatsAggregatorStage(mockPipelineConf.getStatsAggregatorStage()); pipelineConf.getConfiguration().add(new Config("executionMode", ExecutionMode.STANDALONE.name())); pipelineConf.getConfiguration().add(new Config("notifyOnTermination", true)); pipelineConf.getConfiguration().add(new Config("emailIDs", Arrays.asList("foo", "bar"))); pipelineStoreTask.save("admin2", PIPELINE_WITH_EMAIL, ZERO_REV, "description" , pipelineConf); } } catch (PipelineStoreException e) { throw new RuntimeException(e); } return pipelineStoreTask; } } /*************** PipelineStateStore ***************/ @Module(injects = PipelineStateStore.class, library = true, includes = {TestRuntimeModule.class}) public static class TestPipelineStateStoreModule { public TestPipelineStateStoreModule() { } @Provides @Singleton public PipelineStateStore providePipelineStore(RuntimeInfo info, Configuration conf) { PipelineStateStore pipelineStateStore = new FilePipelineStateStore(info, conf); CachePipelineStateStore cachePipelineStateStore = new CachePipelineStateStore(pipelineStateStore, conf); cachePipelineStateStore.init(); return cachePipelineStateStore; } } @Module( injects = {AclStoreTask.class}, library = true, includes = {TestRuntimeModule.class, TestPipelineStoreModuleNew.class} ) public static class TestAclStoreModule { public TestAclStoreModule() { } @Provides @Singleton public AclStoreTask provideAclStore(RuntimeInfo info, PipelineStoreTask pipelineStoreTask) { AclStoreTask aclStoreTask = new FileAclStoreTask(info, pipelineStoreTask, new LockCache<String>()); aclStoreTask.init(); return aclStoreTask; } } /*************** RuntimeInfo ***************/ @Module(library = true) public static class TestRuntimeModule { private static GreenMail server; public static GreenMail getMailServer() { if(server == null) { int port = 25; try { ServerSocket serverSocket = new ServerSocket(0); port = serverSocket.getLocalPort(); serverSocket.close(); } catch (IOException e) { } ServerSetup serverSetup = new ServerSetup(port, "localhost", "smtp"); server = new GreenMail(serverSetup); server.setUser("user@x", "user", "password"); server.start(); } return server; } public TestRuntimeModule() { } @Provides @Singleton public Configuration provideConfiguration() { Configuration conf = new Configuration(); conf.set("mail.smtp.host", "localhost"); conf.set("mail.smtp.port", getMailServer().getSmtp().getPort()); return conf; } @Provides @Singleton public RuntimeInfo provideRuntimeInfo() { RuntimeInfo info = new StandaloneRuntimeInfo(RuntimeModule.SDC_PROPERTY_PREFIX, new MetricRegistry(), Arrays.asList(getClass().getClassLoader())); return info; } @Provides @Singleton public EventListenerManager provideEventListenerManager() { return new EventListenerManager(); } } /*************** SafeScheduledExecutorService ***************/ @Module(library = true) public static class TestExecutorModule { @Provides @Named("previewExecutor") public SafeScheduledExecutorService providePreviewExecutor() { return new SafeScheduledExecutorService(1, "preview"); } @Provides @Named("runnerExecutor") @Singleton public SafeScheduledExecutorService provideRunnerExecutor() { return new SafeScheduledExecutorService(10, "runner"); } @Provides @Named("managerExecutor") @Singleton public SafeScheduledExecutorService provideManagerExecutor() { return new SafeScheduledExecutorService(10, "manager"); } } /*************** PipelineProvider ***************/ @Module(injects = {EmailSender.class, AlertManager.class, Observer.class, RulesConfigLoader.class, ThreadHealthReporter.class, DataObserverRunnable.class, RulesConfigLoaderRunnable.class, MetricObserverRunnable.class, SourceOffsetTracker.class, PipelineRunner.class, com.streamsets.datacollector.execution.runner.common.ProductionPipelineBuilder.class}, library = true, includes = {TestRuntimeModule.class, TestPipelineStoreModuleNew.class, TestSnapshotStoreModule.class}) public static class TestPipelineProviderModule { private String name; private String rev; public TestPipelineProviderModule() { } public TestPipelineProviderModule(String name, String rev) { this.name = name; this.rev = rev; } @Provides @Named("name") public String provideName() { return name; } @Provides @Named("rev") public String provideRev() { return rev; } @Provides @Singleton public MetricRegistry provideMetricRegistry() { return new MetricRegistry(); } @Provides @Singleton public EmailSender provideEmailSender() { return Mockito.mock(EmailSender.class); } @Provides @Singleton public AlertManager provideAlertManager() { return Mockito.mock(AlertManager.class); } @Provides @Singleton public MetricsObserverRunner provideMetricsObserverRunner() { return Mockito.mock(MetricsObserverRunner.class); } @Provides @Singleton public Observer provProductionObserver() { return Mockito.mock(ProductionObserver.class); } @Provides @Singleton public RulesConfigLoader provideRulesConfigLoader() { return Mockito.mock(RulesConfigLoader.class); } @Provides @Singleton public ThreadHealthReporter provideThreadHealthReporter() { return Mockito.mock(ThreadHealthReporter.class); } @Provides @Singleton public RulesConfigLoaderRunnable provideRulesConfigLoaderRunnable() { return Mockito.mock(RulesConfigLoaderRunnable.class); } @Provides @Singleton public MetricObserverRunnable provideMetricObserverRunnable() { return Mockito.mock(MetricObserverRunnable.class); } @Provides @Singleton public DataObserverRunnable provideDataObserverRunnable() { return Mockito.mock(DataObserverRunnable.class); } @Provides @Singleton public SourceOffsetTracker provideProductionSourceOffsetTracker(@Named("name") String name, @Named("rev") String rev, RuntimeInfo runtimeInfo) { return new ProductionSourceOffsetTracker(name, rev, runtimeInfo); } @Provides @Singleton public PipelineRunner provideProductionPipelineRunner(@Named("name") String name, @Named("rev") String rev, Configuration configuration, RuntimeInfo runtimeInfo, MetricRegistry metrics, SnapshotStore snapshotStore, ThreadHealthReporter threadHealthReporter, SourceOffsetTracker sourceOffsetTracker) { return new com.streamsets.datacollector.execution.runner.common.ProductionPipelineRunner(name, rev, configuration, runtimeInfo, metrics, snapshotStore, threadHealthReporter); } @Provides @Singleton public com.streamsets.datacollector.execution.runner.common.ProductionPipelineBuilder provideProductionPipelineBuilder(@Named("name") String name, @Named("rev") String rev, RuntimeInfo runtimeInfo, StageLibraryTask stageLib, PipelineRunner runner, Observer observer) { return new com.streamsets.datacollector.execution.runner.common.ProductionPipelineBuilder(name, rev, new Configuration(), runtimeInfo, stageLib, (ProductionPipelineRunner)runner, observer); } } /*************** Runner ***************/ @Module(injects = Runner.class, library = true, includes = {TestExecutorModule.class, TestPipelineStoreModuleNew.class, TestPipelineStateStoreModule.class, TestPipelineProviderModule.class}) public static class TestRunnerModule { private final String name; private final String rev; private final ObjectGraph objectGraph; public TestRunnerModule(String name, String rev, ObjectGraph objectGraph) { this.name = name; this.rev = rev; this.objectGraph = objectGraph; } @Provides public Runner provideRunner(@Named("runnerExecutor") SafeScheduledExecutorService runnerExecutor) { return new AsyncRunner(new StandaloneRunner(name, rev, objectGraph), runnerExecutor); } } /*************** SnapshotStore ***************/ @Module(injects = SnapshotStore.class, library = true, includes = {TestRuntimeModule.class, LockCacheModule.class}) public static class TestSnapshotStoreModule { @Provides @Singleton public SnapshotStore provideSnapshotStore(RuntimeInfo runtimeInfo, LockCache<String> lockCache) { return new FileSnapshotStore(runtimeInfo, lockCache); } } /*************** PipelineManager ***************/ @Module(injects = {StandaloneAndClusterPipelineManager.class, StandaloneRunner.class}, library = true, includes = {TestPipelineStoreModuleNew.class, TestExecutorModule.class, TestSnapshotStoreModule.class, TestAclStoreModule.class}) public static class TestPipelineManagerModule { public TestPipelineManagerModule() { } @Provides @Singleton public PreviewerProvider providePreviewerProvider() { return new PreviewerProvider() { @Override public Previewer createPreviewer(String user, String name, String rev, PreviewerListener listener, ObjectGraph objectGraph) { Previewer mock = Mockito.mock(Previewer.class); Mockito.when(mock.getId()).thenReturn(UUID.randomUUID().toString()); Mockito.when(mock.getName()).thenReturn(name); Mockito.when(mock.getRev()).thenReturn(rev); return mock; } }; } @Provides @Singleton public RunnerProvider provideRunnerProvider() { return (name, rev, objectGraph, executionMode) -> { ObjectGraph plus = objectGraph.plus(new TestPipelineProviderModule(name, rev)); TestRunnerModule testRunnerModule = new TestRunnerModule(name, rev, plus); return testRunnerModule.provideRunner(new SafeScheduledExecutorService(1, "runnerExecutor")); }; } } }