/*
* Copyright © 2015 Cask Data, Inc.
*
* 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 co.cask.cdap.internal.app.deploy.pipeline;
import co.cask.cdap.api.dataset.Dataset;
import co.cask.cdap.api.dataset.module.DatasetModule;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.lang.ProgramClassLoader;
import co.cask.cdap.common.lang.jar.BundleJarUtil;
import co.cask.cdap.common.utils.DirUtils;
import co.cask.cdap.data2.dataset2.DatasetFramework;
import co.cask.cdap.data2.dataset2.ModuleConflictException;
import co.cask.cdap.data2.dataset2.SingleTypeModule;
import co.cask.cdap.proto.Id;
import org.apache.twill.filesystem.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Map;
/**
* Deploys Dataset Modules.
*/
public class DatasetModulesDeployer {
private static final Logger LOG = LoggerFactory.getLogger(DatasetModulesDeployer.class);
private final CConfiguration cConf;
private final DatasetFramework datasetFramework;
// An instance of InMemoryDatasetFramework is used to check if a dataset is a system dataset
private final DatasetFramework systemDatasetFramework;
private final Id.Namespace namespace;
private final boolean allowDatasetUncheckedUpgrade;
public DatasetModulesDeployer(DatasetFramework datasetFramework, DatasetFramework inMemoryDatasetFramework,
Id.Namespace namespace, CConfiguration cConf) {
this.datasetFramework = datasetFramework;
this.systemDatasetFramework = inMemoryDatasetFramework;
this.namespace = namespace;
this.cConf = cConf;
this.allowDatasetUncheckedUpgrade = cConf.getBoolean(Constants.Dataset.DATASET_UNCHECKED_UPGRADE);
}
/**
* Deploy the given dataset modules.
*
* @param modules the dataset modules to deploy
* @param jarLocation the location of the jar file containing the modules
* @throws Exception if there was a problem deploying a module
*/
public void deployModules(Map<String, String> modules, Location jarLocation) throws Exception {
File tmpDir = new File(cConf.get(Constants.CFG_LOCAL_DATA_DIR),
cConf.get(Constants.AppFabric.TEMP_DIR)).getAbsoluteFile();
File unpackDir = DirUtils.createTempDir(tmpDir);
try (ProgramClassLoader classLoader = getClassLoader(cConf, jarLocation, unpackDir)) {
for (Map.Entry<String, String> moduleEntry : modules.entrySet()) {
// note: using app class loader to load module class
@SuppressWarnings("unchecked")
Class<Dataset> clazz = (Class<Dataset>) classLoader.loadClass(moduleEntry.getValue());
String moduleName = moduleEntry.getKey();
try {
// note: we can deploy module or create module from Dataset class
// note: it seems dangerous to instantiate dataset module here, but this will be fine when we move deploy into
// isolated user's environment (e.g. separate yarn container)
Id.DatasetModule moduleId = Id.DatasetModule.from(namespace, moduleName);
if (DatasetModule.class.isAssignableFrom(clazz)) {
LOG.info("Adding module: {}", clazz.getName());
datasetFramework.addModule(moduleId, (DatasetModule) clazz.newInstance(), jarLocation);
} else if (Dataset.class.isAssignableFrom(clazz)) {
if (!systemDatasetFramework.hasSystemType(clazz.getName())) {
// checking if type is in already or force upgrade is allowed
Id.DatasetType typeId = Id.DatasetType.from(namespace, clazz.getName());
if (!datasetFramework.hasType(typeId) || allowDatasetUncheckedUpgrade) {
LOG.info("Adding module: {}", clazz.getName());
datasetFramework.addModule(moduleId, new SingleTypeModule(clazz), jarLocation);
}
}
} else {
String msg = String.format(
"Cannot use class %s to add dataset module: it must be of type DatasetModule or Dataset",
clazz.getName());
throw new IllegalArgumentException(msg);
}
} catch (ModuleConflictException e) {
LOG.info("Not deploying module " + moduleName + " as it already exists");
}
}
} finally {
try {
DirUtils.deleteDirectoryContents(unpackDir);
} catch (IOException e) {
// OK to ignore. Just log a warn.
LOG.warn("Failed to delete directory {}", unpackDir, e);
}
}
}
private ProgramClassLoader getClassLoader(CConfiguration cConf,
Location jarLocation, File unpackDir) throws IOException {
BundleJarUtil.unJar(jarLocation, unpackDir);
// Create a ProgramClassLoader with the CDAP system ClassLoader as filter parent
return ProgramClassLoader.create(cConf, unpackDir, ApplicationDeployable.class.getClassLoader());
}
}