/**
* 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.configupgrade;
import com.google.common.base.Preconditions;
import com.streamsets.datacollector.config.PipelineConfiguration;
import com.streamsets.datacollector.config.StageConfiguration;
import com.streamsets.datacollector.config.StageDefinition;
import com.streamsets.datacollector.creation.PipelineBeanCreator;
import com.streamsets.datacollector.stagelibrary.StageLibraryTask;
import com.streamsets.datacollector.store.PipelineStoreTask;
import com.streamsets.datacollector.util.ContainerError;
import com.streamsets.datacollector.validation.Issue;
import com.streamsets.datacollector.validation.IssueCreator;
import com.streamsets.datacollector.validation.ValidationError;
import com.streamsets.pipeline.api.Config;
import com.streamsets.pipeline.api.StageException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PipelineConfigurationUpgrader {
private static final Logger LOG = LoggerFactory.getLogger(PipelineConfigurationUpgrader.class);
private static final PipelineConfigurationUpgrader UPGRADER = new PipelineConfigurationUpgrader() {
};
public static PipelineConfigurationUpgrader get() {
return UPGRADER;
}
protected PipelineConfigurationUpgrader() {
}
public PipelineConfiguration upgradeIfNecessary(StageLibraryTask library, PipelineConfiguration pipelineConf, List<Issue> issues) {
Preconditions.checkArgument(issues.size() == 0, "Given list of issues must be empty.");
boolean upgrade;
// Firstly upgrading schema if needed, then data
upgrade = needsSchemaUpgrade(pipelineConf, issues);
if(upgrade && issues.isEmpty()) {
pipelineConf = upgradeSchema(pipelineConf, issues);
}
// Something went wrong with the schema upgrade
if(!issues.isEmpty()) {
return null;
}
// Upgrading data if needed
upgrade = needsUpgrade(library, pipelineConf, issues);
if (upgrade && issues.isEmpty()) {
//we try to upgrade only if we have all defs for the pipelineConf
pipelineConf = upgrade(library, pipelineConf, issues);
}
return (issues.isEmpty()) ? pipelineConf : null;
}
private boolean needsSchemaUpgrade(PipelineConfiguration pipelineConf, List<Issue> ownIssues) {
return pipelineConf.getSchemaVersion() != PipelineStoreTask.SCHEMA_VERSION;
}
private PipelineConfiguration upgradeSchema(PipelineConfiguration pipelineConf, List<Issue> issues) {
LOG.debug("Upgrading schema from version {} on pipeline {}", pipelineConf.getSchemaVersion(), pipelineConf.getUuid());
switch (pipelineConf.getSchemaVersion()) {
case 1:
upgradeSchema1to2(pipelineConf, issues);
// fall through
case 2:
upgradeSchema2to3(pipelineConf, issues);
break;
default:
issues.add(IssueCreator.getPipeline().create(ValidationError.VALIDATION_0000, pipelineConf.getSchemaVersion()));
}
pipelineConf.setSchemaVersion(PipelineStoreTask.SCHEMA_VERSION);
return issues.isEmpty() ? pipelineConf : null;
}
private void upgradeSchema1to2(PipelineConfiguration pipelineConf, List<Issue> issues) {
// Version 1 did not have any eventLanes configuration so this will be null and we need to convert it
// to empty list instead for all stages.
// Normal stages
for(StageConfiguration stage: pipelineConf.getStages()) {
convertEventLaneNullToEmptyList(stage);
}
// Extra stages
convertEventLaneNullToEmptyList(pipelineConf.getErrorStage());
convertEventLaneNullToEmptyList(pipelineConf.getStatsAggregatorStage());
}
private void upgradeSchema2to3(PipelineConfiguration pipelineConf, List<Issue> issues) {
// Added new attribute "pipelineId"
if (pipelineConf.getPipelineId() == null) {
pipelineConf.setPipelineId(pipelineConf.getInfo().getPipelineId());
}
}
private void convertEventLaneNullToEmptyList(StageConfiguration stage) {
if(stage != null && stage.getEventLanes() == null) {
stage.setEventLanes(Collections.<String>emptyList());
}
}
public StageDefinition getPipelineDefinition() {
return PipelineBeanCreator.PIPELINE_DEFINITION;
}
boolean needsUpgrade(StageLibraryTask library, PipelineConfiguration pipelineConf, List<Issue> issues) {
boolean upgrade;
// pipeline confs
StageConfiguration pipelineConfs = PipelineBeanCreator.getPipelineConfAsStageConf(pipelineConf);
upgrade = needsUpgrade(getPipelineDefinition(), pipelineConfs, issues);
// pipeline aggregating sink stage confs
StageConfiguration statsAggTargetConf = pipelineConf.getStatsAggregatorStage();
if (statsAggTargetConf != null) {
StageDefinition def = library.getStage(statsAggTargetConf.getLibrary(), statsAggTargetConf.getStageName(), false);
upgrade |= needsUpgrade(def, statsAggTargetConf, issues);
}
// pipeline error stage confs
StageConfiguration errorStageConf = pipelineConf.getErrorStage();
if (errorStageConf != null) {
StageDefinition def = library.getStage(errorStageConf.getLibrary(), errorStageConf.getStageName(), false);
upgrade |= needsUpgrade(def, errorStageConf, issues);
}
// pipeline stages confs
for (StageConfiguration conf : pipelineConf.getStages()) {
StageDefinition def = library.getStage(conf.getLibrary(), conf.getStageName(), false);
upgrade |= needsUpgrade(def, conf, issues);
}
return upgrade;
}
boolean needsUpgrade(StageDefinition def, StageConfiguration conf, List<Issue> issues) {
boolean upgrade = false;
if (def == null) {
issues.add(IssueCreator.getStage(conf.getInstanceName()).create(ContainerError.CONTAINER_0901, conf.getLibrary(),
conf.getStageName()));
} else {
int versionDiff = def.getVersion() - conf.getStageVersion();
versionDiff = (versionDiff == 0) ? 0 : (versionDiff > 0) ? 1 : -1;
switch (versionDiff) {
case 0: // no change
break;
case 1: // current def is newer
upgrade = true;
break;
case -1: // current def is older
issues.add(IssueCreator.getStage(conf.getInstanceName()).create(ContainerError.CONTAINER_0902,
conf.getLibrary(), conf.getStageName(), def.getVersion(), conf.getStageVersion(), conf.getInstanceName()));
break;
default:
throw new IllegalStateException("Unexpected version diff " + versionDiff);
}
}
return upgrade;
}
PipelineConfiguration upgrade(StageLibraryTask library, PipelineConfiguration pipelineConf, List<Issue> issues) {
List<Issue> ownIssues = new ArrayList<>();
// upgrade pipeline level configs if necessary
StageConfiguration pipelineConfs = PipelineBeanCreator.getPipelineConfAsStageConf(pipelineConf);
if (needsUpgrade(getPipelineDefinition(), pipelineConfs, issues)) {
String sourceName = null;
for (StageConfiguration stageConf: pipelineConf.getStages()) {
if (stageConf.getInputLanes().isEmpty()) {
sourceName = stageConf.getStageName();
}
}
List<Config> configList = pipelineConfs.getConfiguration();
// config 'sourceName' used by upgrader v3 to v4
configList.add(new Config("sourceName", sourceName));
pipelineConfs.setConfig(configList);
pipelineConfs = upgrade(getPipelineDefinition(), pipelineConfs, issues);
configList = pipelineConfs.getConfiguration();
int index = -1;
for (int i = 0; i < configList.size(); i++) {
Config config = configList.get(i);
if (config.getName().equals("sourceName")) {
index = i;
break;
}
}
configList.remove(index);
pipelineConfs.setConfig(configList);
}
List<StageConfiguration> stageConfs = new ArrayList<>();
// upgrade aggregating stage if present and if necessary
StageConfiguration statsAggregatorStageConf = pipelineConf.getStatsAggregatorStage();
if (statsAggregatorStageConf != null) {
StageDefinition def = library.getStage(statsAggregatorStageConf.getLibrary(), statsAggregatorStageConf.getStageName(), false);
if (needsUpgrade(def, statsAggregatorStageConf, ownIssues)) {
statsAggregatorStageConf = upgrade(def, statsAggregatorStageConf, ownIssues);
}
}
// upgrade error stage if present and if necessary
StageConfiguration errorStageConf = pipelineConf.getErrorStage();
if (errorStageConf != null) {
StageDefinition def = library.getStage(errorStageConf.getLibrary(), errorStageConf.getStageName(), false);
if (needsUpgrade(def, errorStageConf, ownIssues)) {
errorStageConf = upgrade(def, errorStageConf, ownIssues);
}
}
// upgrade stages;
for (StageConfiguration stageConf : pipelineConf.getStages()) {
StageDefinition def = library.getStage(stageConf.getLibrary(), stageConf.getStageName(), false);
if (needsUpgrade(def, stageConf, ownIssues)) {
stageConf = upgrade(def, stageConf, ownIssues);
}
if (stageConf != null) {
stageConfs.add(stageConf);
}
}
// if ownIssues > 0 we had an issue upgrading, we wont touch the pipelineConf and return null
if (ownIssues.isEmpty()) {
pipelineConf.setConfiguration(pipelineConfs.getConfiguration());
pipelineConf.setVersion(pipelineConfs.getStageVersion());
pipelineConf.setErrorStage(errorStageConf);
pipelineConf.setStatsAggregatorStage(statsAggregatorStageConf);
pipelineConf.setStages(stageConfs);
} else {
issues.addAll(ownIssues);
pipelineConf = null;
}
return pipelineConf;
}
StageConfiguration upgrade(StageDefinition def, StageConfiguration conf, List<Issue> issues) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
int fromVersion = conf.getStageVersion();
int toVersion = def.getVersion();
try {
Thread.currentThread().setContextClassLoader(def.getStageClassLoader());
LOG.warn("Upgraded instance '{}' from version '{}' to version '{}'", conf.getInstanceName(), fromVersion,
toVersion);
List<Config> configs = def.getUpgrader().upgrade(def.getLibrary(), def.getName(), conf.getInstanceName(),
fromVersion, toVersion, conf.getConfiguration());
conf.setStageVersion(def.getVersion());
conf.setConfig(configs);
} catch (StageException ex) {
issues.add(IssueCreator.getStage(conf.getInstanceName()).create(ex.getErrorCode(), ex.getParams()));
} catch (Exception ex) {
LOG.error("Unknown exception during upgrade: " + ex, ex);
issues.add(IssueCreator.getStage(conf.getInstanceName()).create(ContainerError.CONTAINER_0900, fromVersion,
toVersion, ex.toString()));
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
return conf;
}
}