blob: 8522238dc86d99009a58067d3f54bffcea4e2cc4 [file] [log] [blame]
/**
* Licensed to 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 org.apache.oozie.action.hadoop;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapred.Counters;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobID;
import org.apache.hadoop.mapred.RunningJob;
import org.apache.oozie.action.ActionExecutorException;
import org.apache.oozie.client.WorkflowAction;
import org.apache.oozie.util.XConfiguration;
import org.apache.oozie.util.XLog;
import org.apache.oozie.util.XmlUtils;
import org.jdom.Element;
import org.jdom.Namespace;
public class SqoopActionExecutor extends JavaActionExecutor {
public static final String OOZIE_ACTION_EXTERNAL_STATS_WRITE = "oozie.action.external.stats.write";
@VisibleForTesting
static final String OOZIE_ACTION_SQOOP_SHELLSPLITTER = "oozie.action.sqoop.shellsplitter";
private static final boolean SHELLSPLITTER_DEFAULT = false;
private static final String SQOOP_MAIN_CLASS_NAME = "org.apache.oozie.action.hadoop.SqoopMain";
static final String SQOOP_ARGS = "oozie.sqoop.args";
private static final String SQOOP = "sqoop";
private ShellSplitter shellSplitter = new ShellSplitter();
public SqoopActionExecutor() {
super(SQOOP);
}
@Override
public List<Class<?>> getLauncherClasses() {
List<Class<?>> classes = new ArrayList<>();
try {
classes.add(Class.forName(SQOOP_MAIN_CLASS_NAME));
}
catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found", e);
}
return classes;
}
@Override
protected String getLauncherMain(Configuration launcherConf, Element actionXml) {
return launcherConf.get(LauncherAMUtils.CONF_OOZIE_ACTION_MAIN_CLASS, SQOOP_MAIN_CLASS_NAME);
}
@Override
Configuration setupActionConf(Configuration actionConf, Context context, Element actionXml, Path appPath)
throws ActionExecutorException {
super.setupActionConf(actionConf, context, actionXml, appPath);
Namespace ns = actionXml.getNamespace();
try {
Element e = actionXml.getChild("configuration", ns);
if (e != null) {
String strConf = XmlUtils.prettyPrint(e).toString();
XConfiguration inlineConf = new XConfiguration(new StringReader(strConf));
XConfiguration.copy(inlineConf, actionConf);
checkForDisallowedProps(inlineConf, "inline configuration");
}
} catch (IOException ex) {
throw convertException(ex);
}
List<String> argList = new ArrayList<>();
// Build a list of arguments from either a tokenized <command> string or a list of <arg>
if (actionXml.getChild("command", ns) != null) {
String command = actionXml.getChild("command", ns).getTextTrim();
argList = splitCommand(actionConf, command);
}
else {
@SuppressWarnings("unchecked")
List<Element> eArgs = (List<Element>) actionXml.getChildren("arg", ns);
for (Element elem : eArgs) {
argList.add(elem.getTextTrim());
}
}
// If the command is given accidentally as "sqoop import --option"
// instead of "import --option" we can make a user's life easier
// by removing away the unnecessary "sqoop" token.
// However, we do not do this if the command looks like
// "sqoop --option", as that's entirely invalid.
if (argList.size() > 1 &&
argList.get(0).equalsIgnoreCase(SQOOP) &&
!argList.get(1).startsWith("-")) {
XLog.getLog(getClass()).info(
"Found a redundant 'sqoop' prefixing the command. Removing it.");
argList.remove(0);
}
setSqoopCommand(actionConf, argList.toArray(new String[argList.size()]));
return actionConf;
}
private List<String> splitCommand(Configuration actionConf, String command) throws ActionExecutorException {
List<String> argList;
boolean useShellSplitter = actionConf.getBoolean(OOZIE_ACTION_SQOOP_SHELLSPLITTER, SHELLSPLITTER_DEFAULT);
if (useShellSplitter) {
try {
argList = shellSplitter.split(command);
} catch (ShellSplitterException e) {
throw new ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "SQOOP002",
"Cannot parse sqoop command: [{0}]", command, e);
}
}
else {
argList = splitBySpace(command);
}
return argList;
}
private List<String> splitBySpace(final String command) {
List<String> tokens = new ArrayList<>();
StringTokenizer st = new StringTokenizer(command, " ");
while (st.hasMoreTokens()) {
tokens.add(st.nextToken());
}
return tokens;
}
private void setSqoopCommand(Configuration conf, String[] args) {
ActionUtils.setStrings(conf, SQOOP_ARGS, args);
}
/**
* We will gather counters from all executed action Hadoop jobs (e.g. jobs
* that moved data, not the launcher itself) and merge them together. There
* will be only one job most of the time. The only exception is
* import-all-table option that will execute one job per one exported table.
*
* @param context Action context
* @param action Workflow action
* @throws ActionExecutorException thrown if action end execution fails.
*/
@Override
public void end(Context context, WorkflowAction action) throws ActionExecutorException {
super.end(context, action);
JobClient jobClient = null;
boolean exception = false;
try {
if (action.getStatus() == WorkflowAction.Status.OK) {
Element actionXml = XmlUtils.parseXml(action.getConf());
Configuration jobConf = createBaseHadoopConf(context, actionXml);
jobClient = createJobClient(context, jobConf);
// Cumulative counters for all Sqoop mapreduce jobs
Counters counters = null;
// Sqoop do not have to create mapreduce job each time
String externalIds = action.getExternalChildIDs();
if (externalIds != null && !externalIds.trim().isEmpty()) {
String []jobIds = externalIds.split(",");
for(String jobId : jobIds) {
RunningJob runningJob = jobClient.getJob(JobID.forName(jobId));
if (runningJob == null) {
throw new ActionExecutorException(ActionExecutorException.ErrorType.FAILED, "SQOOP001",
"Unknown hadoop job [{0}] associated with action [{1}]. Failing this action!", action
.getExternalId(), action.getId());
}
Counters taskCounters = runningJob.getCounters();
if(taskCounters != null) {
if(counters == null) {
counters = taskCounters;
} else {
counters.incrAllCounters(taskCounters);
}
} else {
XLog.getLog(getClass()).warn("Could not find Hadoop Counters for job: [{0}]", jobId);
}
}
}
if (counters != null) {
ActionStats stats = new MRStats(counters);
String statsJsonString = stats.toJSON();
context.setVar(MapReduceActionExecutor.HADOOP_COUNTERS, statsJsonString);
// If action stats write property is set to false by user or
// size of stats is greater than the maximum allowed size,
// do not store the action stats
if (Boolean.parseBoolean(evaluateConfigurationProperty(actionXml,
OOZIE_ACTION_EXTERNAL_STATS_WRITE, "true"))
&& (statsJsonString.getBytes(StandardCharsets.UTF_8).length <= getMaxExternalStatsSize())) {
context.setExecutionStats(statsJsonString);
LOG.debug(
"Printing stats for sqoop action as a JSON string : [{0}]", statsJsonString);
}
} else {
context.setVar(MapReduceActionExecutor.HADOOP_COUNTERS, "");
XLog.getLog(getClass()).warn("Can't find any associated Hadoop job counters");
}
}
}
catch (Exception ex) {
exception = true;
throw convertException(ex);
}
finally {
if (jobClient != null) {
try {
jobClient.close();
}
catch (Exception e) {
if (exception) {
LOG.error("JobClient error: ", e);
}
else {
throw convertException(e);
}
}
}
}
}
// Return the value of the specified configuration property
private String evaluateConfigurationProperty(Element actionConf, String key, String defaultValue)
throws ActionExecutorException {
try {
if (actionConf != null) {
Namespace ns = actionConf.getNamespace();
Element e = actionConf.getChild("configuration", ns);
if(e != null) {
String strConf = XmlUtils.prettyPrint(e).toString();
XConfiguration inlineConf = new XConfiguration(new StringReader(strConf));
return inlineConf.get(key, defaultValue);
}
}
return defaultValue;
}
catch (IOException ex) {
throw convertException(ex);
}
}
/**
* Return the sharelib name for the action.
*
* @return returns <code>sqoop</code>.
* @param actionXml action xml element
*/
@Override
protected String getDefaultShareLibName(Element actionXml) {
return "sqoop";
}
}