/*
* Copyright 2014-2015 the original author or authors.
*
* 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 org.springframework.xd.sqoop;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.sqoop.Sqoop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.data.hadoop.configuration.ConfigurationFactoryBean;
import org.springframework.data.hadoop.configuration.ConfigurationUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Class for running Sqoop tool.
*
* @since 1.1
* @author Thomas Risberg
*/
public class SqoopRunner {
private static final Logger logger = LoggerFactory.getLogger(SqoopRunner.class);
private static final String JDBC_USERNAME_KEY = "jdbc.username";
private static final String JDBC_PASSWORD_KEY = "jdbc.password";
private static final String JDBC_URL_KEY = "jdbc.url";
private static final String SPRING_HADOOP_CONFIG_PREFIX = "spring.hadoop.config";
public static final String SECURITY_AUTH_METHOD = "security.authMethod";
public static final String SECURITY_USER_KEYTAB = "security.userKeytab";
public static final String SECURITY_USER_PRINCIPAL = "security.userPrincipal";
public static final String SECURITY_NAMENODE_PRINCIPAL = "security.namenodePrincipal";
public static final String SECURITY_RM_MANAGER_PRINCIPAL = "security.rmManagerPrincipal";
public static final String SECURITY_MAPREDUCE_JOBHISTORY_PRINCIPAL = "security.jobHistoryPrincipal";
public static void main(String[] args) {
if (args == null || args.length < 1) {
throw new IllegalArgumentException("Missing arguments and/or configuration options for Sqoop");
}
String jarPath = org.apache.sqoop.util.Jars.getJarPathForClass(JobConf.class);
String hadoopMapredHome = jarPath.substring(0, jarPath.lastIndexOf(File.separator));
String command = args[0];
String[] sqoopArgumentSource = args[1].split(" ");
List<String> sqoopArguments = new ArrayList<String>();
boolean provideConnect = parseSqoopCommandArguments(command, sqoopArgumentSource, sqoopArguments);
logger.info("Sqoop command: " + command);
logger.info("Using args: " + sqoopArguments);
logger.info("Mapreduce home: " + hadoopMapredHome);
Map<String, String> configOptions = new HashMap<String, String>();
parseConfigOptions(args, configOptions);
Configuration configuration = createConfiguration(configOptions);
List<String> finalArguments = new ArrayList<String>();
createFinalArguments(hadoopMapredHome, command, sqoopArguments, provideConnect, configOptions, finalArguments);
String xdModuleLibjars = System.getenv("XD_MODULE_LIBJARS");
if (StringUtils.hasText(xdModuleLibjars)) {
logger.info("Adding --libjars to Sqoop job submission");
String[] extraJars = StringUtils.commaDelimitedListToStringArray(xdModuleLibjars);
List<URL> classPathUrls = new ArrayList<URL>();
try {
classPathUrls.addAll(Arrays.asList(((URLClassLoader) SqoopRunner.class.getClassLoader()).getURLs()));
} catch (Exception ignore) {
}
List<Resource> libJars = new ArrayList<>();
for (String jarName : extraJars) {
for (URL url : classPathUrls) {
if (url.getPath().endsWith(jarName)) {
logger.info("Adding jar: " + url);
libJars.add(new UrlResource(url));
}
}
}
ConfigurationUtils.addLibs(configuration, libJars.toArray(new Resource[libJars.size()]));
}
if (sqoopArguments.contains("--as-avrodatafile")) {
logger.info("Adding Avro libraries to Sqoop job submission");
List<URL> classPathUrls = new ArrayList<URL>();
try {
classPathUrls.addAll(Arrays.asList(((URLClassLoader) SqoopRunner.class.getClassLoader()).getURLs()));
} catch (Exception ignore) {
}
List<Resource> libJars = new ArrayList<>();
for (URL url : classPathUrls) {
if (url.getPath().contains("avro-mapred-1.7") || url.getPath().contains("avro-1.7")) {
logger.info("Adding jar: " + url);
libJars.add(new UrlResource(url));
}
}
ConfigurationUtils.addLibs(configuration, libJars.toArray(new Resource[libJars.size()]));
}
final int ret = Sqoop.runTool(finalArguments.toArray(new String[finalArguments.size()]), configuration);
if (ret != 0) {
throw new RuntimeException("Sqoop failed - return code "
+ Integer.toString(ret));
}
}
protected static void createFinalArguments(String hadoopMapredHome, String command, List<String> sqoopArguments,
boolean provideConnect, Map<String, String> configOptions,
List<String> finalArguments) {
finalArguments.add(command);
if (provideConnect) {
finalArguments.add("--connect=" + configOptions.get(JDBC_URL_KEY));
if (configOptions.containsKey(JDBC_USERNAME_KEY) && configOptions.get(JDBC_USERNAME_KEY) != null) {
finalArguments.add("--username=" + configOptions.get(JDBC_USERNAME_KEY));
}
if (configOptions.containsKey(JDBC_PASSWORD_KEY) && configOptions.get(JDBC_PASSWORD_KEY) != null) {
finalArguments.add("--password=" + configOptions.get(JDBC_PASSWORD_KEY));
}
}
if (command.toLowerCase().startsWith("import") || command.toLowerCase().startsWith("export") ||
command.toLowerCase().startsWith("create")) {
finalArguments.add("--hadoop-mapred-home=" + hadoopMapredHome);
}
finalArguments.addAll(sqoopArguments);
}
protected static Configuration createConfiguration(Map<String, String> configOptions) {
Configuration configuration = new Configuration();
setConfigurationProperty(configOptions, configuration, CommonConfigurationKeys.FS_DEFAULT_NAME_KEY);
setConfigurationProperty(configOptions, configuration, YarnConfiguration.RM_HOSTNAME);
setConfigurationProperty(configOptions, configuration, YarnConfiguration.RM_ADDRESS);
setConfigurationProperty(configOptions, configuration, YarnConfiguration.RM_SCHEDULER_ADDRESS);
setConfigurationProperty(configOptions, configuration, YarnConfiguration.YARN_APPLICATION_CLASSPATH);
setConfigurationProperty(configOptions, configuration, "mapreduce.framework.name");
if (StringUtils.hasText(configOptions.get("mapreduce.jobhistory.address"))) {
setConfigurationProperty(configOptions, configuration, "mapreduce.jobhistory.address");
}
if (configOptions.containsKey(SECURITY_AUTH_METHOD) && "kerberos".equals(configOptions.get(SECURITY_AUTH_METHOD))) {
configuration.setBoolean("hadoop.security.authorization", true);
configuration.set("hadoop.security.authentication", configOptions.get(SECURITY_AUTH_METHOD));
configuration.set("dfs.namenode.kerberos.principal", configOptions.get(SECURITY_NAMENODE_PRINCIPAL));
configuration.set("yarn.resourcemanager.principal", configOptions.get(SECURITY_RM_MANAGER_PRINCIPAL));
if (StringUtils.hasText(configOptions.get(SECURITY_MAPREDUCE_JOBHISTORY_PRINCIPAL))) {
configuration.set("mapreduce.jobhistory.principal", configOptions.get(SECURITY_MAPREDUCE_JOBHISTORY_PRINCIPAL));
}
String userKeytab = configOptions.get(SECURITY_USER_KEYTAB);
String userPrincipal = configOptions.get(SECURITY_USER_PRINCIPAL);
UserGroupInformation.setConfiguration(configuration);
if (StringUtils.hasText(userKeytab)) {
configuration.set(ConfigurationFactoryBean.USERKEYTAB, userKeytab.trim());
}
if (StringUtils.hasText(userPrincipal)) {
configuration.set(ConfigurationFactoryBean.USERPRINCIPAL, userPrincipal.trim());
}
if (StringUtils.hasText(userKeytab) && StringUtils.hasText(userPrincipal)) {
try {
SecurityUtil.login(configuration, ConfigurationFactoryBean.USERKEYTAB, ConfigurationFactoryBean.USERPRINCIPAL);
} catch (Exception e) {
logger.warn("Cannot login using keytab " + userKeytab + " and principal " + userPrincipal, e);
}
}
}
for (Entry<String, String> entry : configOptions.entrySet()) {
String key = entry.getKey();
if (key.startsWith(SPRING_HADOOP_CONFIG_PREFIX + ".")) {
String prop = key.substring(SPRING_HADOOP_CONFIG_PREFIX.length() + 1);
String value = entry.getValue();
logger.info("Setting configuration property: " + prop + "=" + value);
configuration.set(prop, value);
}
}
return configuration;
}
private static void setConfigurationProperty(Map<String, String> configOptions, Configuration configuration, String key) {
if (configOptions.containsKey(key) && StringUtils.hasText(configOptions.get(key))) {
String value = configOptions.get(key);
logger.info("Setting configuration property: " + key + "=" + value);
configuration.set(key, value);
}
}
protected static void parseConfigOptions(String[] args, Map<String, String> configOptions) {
for (int i = 2; i < args.length; i++) {
String option = args[i];
if (!option.contains("=")) {
throw new IllegalArgumentException("Invalid config option provided: " + option);
}
String[] optionParts = option.split("=");
StringBuilder value = new StringBuilder();
if (optionParts.length > 1) {
for (int j = 1; j < optionParts.length; j++) {
if (j > 1) {
value.append("=");
}
value.append(optionParts[j]);
}
}
configOptions.put(optionParts[0], value.length() > 1 ? value.toString() : null);
}
if (logger.isDebugEnabled()) {
logger.debug("Config options: " + configOptions);
}
}
protected static boolean parseSqoopCommandArguments(String command, String[] sqoopArguments, List<String> runtimeArguments) {
boolean connectProvided = false;
boolean connectNeeded = false;
if (command.toLowerCase().startsWith("import") || command.toLowerCase().startsWith("export") ||
command.toLowerCase().startsWith("create") || command.toLowerCase().startsWith("codegen") ||
command.toLowerCase().startsWith("list")) {
connectNeeded = true;
}
for (int i = 0; i < sqoopArguments.length; i++) {
if (sqoopArguments[i].startsWith("--connect") || sqoopArguments[i].startsWith("--options-file")) {
connectProvided = true;
}
if (sqoopArguments[i].contains("\"")) {
StringBuilder quotedArg = new StringBuilder();
while(i < sqoopArguments.length) {
if (quotedArg.length() > 0) {
quotedArg.append(" ");
}
quotedArg.append(sqoopArguments[i]);
if (sqoopArguments[i].endsWith("\"")) {
break;
}
i++;
}
runtimeArguments.add(quotedArg.toString());
}
else {
runtimeArguments.add(sqoopArguments[i]);
}
}
return connectNeeded && !connectProvided;
}
}