/**
* 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.validation;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.streamsets.datacollector.config.PipelineConfiguration;
import com.streamsets.datacollector.config.StageConfiguration;
import com.streamsets.datacollector.config.StageDefinition;
import com.streamsets.datacollector.configupgrade.PipelineConfigurationUpgrader;
import com.streamsets.datacollector.runner.MockStages;
import com.streamsets.datacollector.stagelibrary.StageLibraryTask;
import com.streamsets.pipeline.api.Config;
import com.streamsets.pipeline.api.ExecutionMode;
import com.streamsets.pipeline.api.StageException;
import com.streamsets.pipeline.api.StageUpgrader;
import com.streamsets.pipeline.api.impl.TextUtils;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.LinkedList;
import java.util.List;
public class TestPipelineConfigurationValidator {
@Test
public void testValidConfiguration() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertFalse(validator.validate().getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
Assert.assertFalse(validator.getIssues().hasIssues());
Assert.assertTrue(validator.getOpenLanes().isEmpty());
}
@Test
public void testEmptyTitle() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget();
conf.setTitle("");
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertTrue(validator.validate().getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
Assert.assertTrue(validator.getIssues().hasIssues());
Assert.assertTrue(validator.getOpenLanes().isEmpty());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0093.name(), issues.get(0).getErrorCode());
}
@Test
public void testRequiredInactiveConfig() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineWithRequiredDependentConfig();
StageConfiguration stageConf = conf.getStages().get(0);
stageConf.setConfig(
Lists.newArrayList(new Config("dependencyConfName", 0),
new Config("triggeredConfName", null)));
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertFalse(validator.validate().getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
Assert.assertFalse(validator.getIssues().hasIssues());
Assert.assertTrue(validator.getOpenLanes().isEmpty());
stageConf.setConfig(
Lists.newArrayList(new Config("dependencyConfName", 1),
new Config("triggeredConfName", null)));
validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertTrue(validator.validate().getIssues().hasIssues());
Assert.assertFalse(validator.canPreview());
Assert.assertTrue(validator.getIssues().hasIssues());
Assert.assertTrue(validator.getOpenLanes().isEmpty());
}
@Test
public void testSpaceInName() {
Assert.assertTrue(TextUtils.isValidName("Hello World"));
}
@Test
public void testInvalidSchemaVersion() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget(0);
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertTrue(validator.validate().getIssues().hasIssues());
Assert.assertFalse(validator.canPreview());
Assert.assertTrue(validator.getIssues().hasIssues());
Assert.assertTrue(validator.getIssues().getPipelineIssues().get(0).getMessage().contains("VALIDATION_0000"));
}
@Test
public void testExecutionModes() {
StageLibraryTask lib = MockStages.createStageLibrary();
// cluster only stage can not preview/run as standalone
PipelineConfiguration conf = MockStages.createPipelineConfigurationWithClusterOnlyStage(ExecutionMode.STANDALONE);
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertTrue(validator.validate().getIssues().hasIssues());
Assert.assertFalse(validator.canPreview());
Assert.assertTrue(validator.getIssues().hasIssues());
// cluster only stage can preview and run as cluster
conf = MockStages.createPipelineConfigurationWithClusterOnlyStage(ExecutionMode.CLUSTER_BATCH);
validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertFalse(validator.validate().getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
Assert.assertFalse(validator.getIssues().hasIssues());
}
@Test
public void testUpgradeIssues() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget();
conf.setVersion(conf.getVersion() + 1); //a version we don't handle
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertTrue(validator.validate().getIssues().hasIssues());
Assert.assertTrue(validator.getIssues().hasIssues());
}
@Test
public void testUpgradeOK() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget();
// tweak validator upgrader to require upgrading the pipeline
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
validator = Mockito.spy(validator);
PipelineConfigurationUpgrader upgrader = Mockito.spy(new PipelineConfigurationUpgrader(){});
int currentVersion = upgrader.getPipelineDefinition().getVersion();
StageDefinition pipelineDef = Mockito.spy(upgrader.getPipelineDefinition());
Mockito.when(pipelineDef.getVersion()).thenReturn(currentVersion + 1);
Mockito.when(pipelineDef.getUpgrader()).thenReturn(new StageUpgrader() {
@Override
public List<Config> upgrade(String library, String stageName, String stageInstance, int fromVersion,
int toVersion,
List<Config> configs) throws StageException {
return configs;
}
});
Mockito.when(upgrader.getPipelineDefinition()).thenReturn(pipelineDef);
Mockito.when(validator.getUpgrader()).thenReturn(upgrader);
Assert.assertFalse(validator.validate().getIssues().hasIssues());
Assert.assertEquals(currentVersion + 1, conf.getVersion());;
}
@Test
public void testLibraryAlias() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget();
StageConfiguration stageConf = conf.getStages().get(0);
String stageLib = stageConf.getLibrary();
String stageName = stageConf.getStageName();
stageConf.setLibrary("fooLib");
stageConf.setStageName("fooStage");
lib = Mockito.spy(lib);
Mockito.when(lib.getLibraryNameAliases()).thenReturn(ImmutableMap.of("fooLib", stageLib));
Mockito.when(lib.getStageNameAliases()).thenReturn(ImmutableMap.of(Joiner.on(",").join(stageLib, "fooStage"),
Joiner.on(",").join(stageLib, stageName)));
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertFalse(String.valueOf(conf.getIssues().getIssues()), conf.getIssues().hasIssues());
Assert.assertEquals(stageLib, conf.getStages().get(0).getLibrary());
}
@Test
public void testEmptyValueRequiredField() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfTargetWithReqField();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertTrue(conf.getIssues().hasIssues());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0007.name(), issues.get(0).getErrorCode());
}
@Test
public void testEmptyValueRequiredMapField() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfTargetWithRequiredMapField();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertTrue(conf.getIssues().hasIssues());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0007.name(), issues.get(0).getErrorCode());
}
@Test
public void testInvalidRequiredFieldsName() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceTargetWithRequiredFields();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertTrue(conf.getIssues().hasIssues());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0033.name(), issues.get(0).getErrorCode());
}
@Test
public void testAddMissingConfigs() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTarget();
// Generate error stage and clean up it's configuration
StageConfiguration errorStage = MockStages.getErrorStageConfig();
errorStage.setConfig(new LinkedList<Config>());
conf.setErrorStage(errorStage);
int pipelineConfigs = conf.getConfiguration().size();
int stageConfigs = conf.getStages().get(2).getConfiguration().size();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
Assert.assertFalse(validator.validate().getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
Assert.assertFalse(validator.getIssues().hasIssues());
Assert.assertTrue(validator.getOpenLanes().isEmpty());
Assert.assertTrue(pipelineConfigs < conf.getConfiguration().size());
Assert.assertTrue(stageConfigs < conf.getStages().get(2).getConfiguration().size());
// Verify that error stage configs were generated as expected
Assert.assertTrue(conf.getErrorStage().getConfiguration().size() > 0);
}
@Test
public void testValidatePipelineConfigs() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationWithClusterOnlyStage(ExecutionMode.CLUSTER_MESOS_STREAMING);
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertTrue(conf.getIssues().hasIssues());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(2, issues.size());
//mesosDispatcherURL is required but not set
Assert.assertEquals(ValidationError.VALIDATION_0007.name(), issues.get(0).getErrorCode());
//hdfsS3ConfDir is required but not set
Assert.assertEquals(ValidationError.VALIDATION_0007.name(), issues.get(1).getErrorCode());
}
@Test
public void testValidateOffsetControlMultipleTargets() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineWith2OffsetCommitController(ExecutionMode.STANDALONE);
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertTrue(conf.getIssues().hasIssues());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0091.name(), issues.get(0).getErrorCode());
}
@Test
public void testValidateOffsetControlDeliveryGuarantee() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineWithOffsetCommitController(ExecutionMode.STANDALONE);
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
conf = validator.validate();
Assert.assertTrue(conf.getIssues().hasIssues());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0092.name(), issues.get(0).getErrorCode());
}
// Having event lane empty when source is declaring events is acceptable
@Test
public void testPipelineWithOpenEventLane() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceTargetWithEventsOpen();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
validator.validate();
Assert.assertFalse(validator.getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
}
// Stages should be re-ordered such that event stages are called when all it's input are properly processed
@Test
public void testPipelineWithConnectedEventLane() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceTargetWithEventsProcessedUnsorted();
// The pipeline should declare the event target as first
Assert.assertEquals("e", conf.getStages().get(0).getInstanceName());
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
validator.validate();
Assert.assertFalse(validator.getIssues().hasIssues());
Assert.assertTrue(validator.canPreview());
List<StageConfiguration> stages = conf.getStages();
Assert.assertNotNull(stages);
Assert.assertEquals(3, stages.size());
// Source should be ordered first (even though that the pipeline declares the event target as first)
Assert.assertEquals("s", stages.get(0).getInstanceName());
// Verify that event stages are marked accordingly
Assert.assertFalse(stages.get(0).isInEventPath());
Assert.assertFalse(stages.get(1).isInEventPath());
Assert.assertTrue(stages.get(2).isInEventPath());
}
// Stage having event lane without declaring support for event is an error
@Test
public void testPipelineDeclaredEventLaneWithoutSupportingEvents() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceTargetDeclaredEventLaneWithoutSupportingEvents();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
validator.validate();
Assert.assertTrue(validator.getIssues().hasIssues());
Assert.assertFalse(validator.canPreview());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(2, issues.size());
// Event lane declared on stage that doesn't produce events
Assert.assertEquals(ValidationError.VALIDATION_0102.name(), issues.get(0).getErrorCode());
// The definition contains open lane "e" that is not connected anywhere
Assert.assertEquals(ValidationError.VALIDATION_0104.name(), issues.get(1).getErrorCode());
}
// Stage having event lane without declaring support for event is an error
@Test
public void testPipelineMergingEventAndDataLanes() {
StageLibraryTask lib = MockStages.createStageLibrary();
PipelineConfiguration conf = MockStages.createPipelineConfigurationSourceProcessorTargetWithMergingEventAndDataLane();
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(lib, "name", conf);
validator.validate();
Assert.assertTrue(validator.getIssues().hasIssues());
Assert.assertFalse(validator.canPreview());
List<Issue> issues = conf.getIssues().getIssues();
Assert.assertEquals(1, issues.size());
Assert.assertEquals(ValidationError.VALIDATION_0103.name(), issues.get(0).getErrorCode());
}
}