001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.action.hadoop;
020
021import java.util.List;
022
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.fs.Path;
025import org.apache.oozie.action.ActionExecutorException;
026import org.jdom.Element;
027import org.jdom.Namespace;
028
029public class ShellActionExecutor extends JavaActionExecutor {
030
031    /**
032     * Config property name to set the child environment
033     */
034    public String OOZIE_LAUNCHER_CHILD_ENV = "mapred.child.env";
035
036    public ShellActionExecutor() {
037        super("shell");
038    }
039
040    @Override
041    protected List<Class> getLauncherClasses() {
042        List<Class> classes = super.getLauncherClasses();
043        // Base class of ShellMain dedicated for 'shell' action
044        classes.add(LauncherMain.class);
045        // Some utility methods used in ShelltMain
046        classes.add(MapReduceMain.class);
047        // Specific to Shell action
048        classes.add(ShellMain.class);
049        // ShellMain's inner class
050        classes.add(ShellMain.OutputWriteThread.class);
051        return classes;
052    }
053
054    @Override
055    protected String getLauncherMain(Configuration launcherConf, Element actionXml) {
056        return launcherConf.get(LauncherMapper.CONF_OOZIE_ACTION_MAIN_CLASS, ShellMain.class.getName());
057    }
058
059    @SuppressWarnings("unchecked")
060    @Override
061    Configuration setupActionConf(Configuration actionConf, Context context, Element actionXml, Path appPath)
062            throws ActionExecutorException {
063        super.setupActionConf(actionConf, context, actionXml, appPath);
064        Namespace ns = actionXml.getNamespace();
065
066        String exec = actionXml.getChild("exec", ns).getTextTrim();
067        String execName = new Path(exec).getName();
068        actionConf.set(ShellMain.CONF_OOZIE_SHELL_EXEC, execName);
069
070        // Setting Shell command's arguments
071        setListInConf("argument", actionXml, actionConf, ShellMain.CONF_OOZIE_SHELL_ARGS, false);
072        // Setting Shell command's environment variable key=value
073        setListInConf("env-var", actionXml, actionConf, ShellMain.CONF_OOZIE_SHELL_ENVS, true);
074
075        // Setting capture output flag
076        actionConf.setBoolean(ShellMain.CONF_OOZIE_SHELL_CAPTURE_OUTPUT,
077                actionXml.getChild("capture-output", ns) != null);
078
079        return actionConf;
080    }
081
082    /**
083     * This method read a list of tag from an XML element and set the
084     * Configuration accordingly
085     *
086     * @param tag
087     * @param actionXml
088     * @param actionConf
089     * @param key
090     * @param checkKeyValue
091     * @throws ActionExecutorException
092     */
093    protected void setListInConf(String tag, Element actionXml, Configuration actionConf, String key,
094            boolean checkKeyValue) throws ActionExecutorException {
095        String[] strTagValue = null;
096        Namespace ns = actionXml.getNamespace();
097        List<Element> eTags = actionXml.getChildren(tag, ns);
098        if (eTags != null && eTags.size() > 0) {
099            strTagValue = new String[eTags.size()];
100            for (int i = 0; i < eTags.size(); i++) {
101                strTagValue[i] = eTags.get(i).getTextTrim();
102                if (checkKeyValue) {
103                    checkPair(strTagValue[i]);
104                }
105            }
106        }
107        MapReduceMain.setStrings(actionConf, key, strTagValue);
108    }
109
110    /**
111     * Check if the key=value pair is appropriately formatted
112     * @param pair
113     * @throws ActionExecutorException
114     */
115    private void checkPair(String pair) throws ActionExecutorException {
116        String[] varValue = pair.split("=");
117        if (varValue == null || varValue.length <= 1) {
118            throw new ActionExecutorException(ActionExecutorException.ErrorType.FAILED, "JA010",
119                    "Wrong ENV format [{0}] in <env-var> , key=value format expected ", pair);
120        }
121    }
122
123    @Override
124    protected Configuration setupLauncherConf(Configuration conf, Element actionXml, Path appPath, Context context)
125            throws ActionExecutorException {
126        super.setupLauncherConf(conf, actionXml, appPath, context);
127        conf.setBoolean("mapreduce.job.complete.cancel.delegation.tokens", true);
128        addDefaultChildEnv(conf);
129        return conf;
130    }
131
132    /**
133     * This method sets the PATH to current working directory for the launched
134     * map task from where shell command will run.
135     *
136     * @param conf
137     */
138    protected void addDefaultChildEnv(Configuration conf) {
139        String envValues = "PATH=.:$PATH";
140        updateProperty(conf, OOZIE_LAUNCHER_CHILD_ENV, envValues);
141    }
142
143    /**
144     * Utility method to append the new value to any property.
145     *
146     * @param conf
147     * @param propertyName
148     * @param appendValue
149     */
150    private void updateProperty(Configuration conf, String propertyName, String appendValue) {
151        if (conf != null) {
152            String val = conf.get(propertyName, "");
153            if (val.length() > 0) {
154                val += ",";
155            }
156            val += appendValue;
157            conf.set(propertyName, val);
158            log.debug("action conf is updated with default value for property " + propertyName + ", old value :"
159                    + conf.get(propertyName, "") + ", new value :" + val);
160        }
161    }
162
163}