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.service;
020
021import com.google.common.base.Preconditions;
022import com.google.common.base.Strings;
023
024import org.apache.commons.lang.StringUtils;
025import org.apache.hadoop.fs.FileStatus;
026import org.apache.hadoop.io.Text;
027import org.apache.hadoop.mapred.Master;
028import org.apache.hadoop.mapred.JobClient;
029import org.apache.hadoop.mapred.JobConf;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.mapreduce.v2.api.HSClientProtocol;
034import org.apache.hadoop.mapreduce.v2.api.MRClientProtocol;
035import org.apache.hadoop.mapreduce.v2.api.protocolrecords.GetDelegationTokenRequest;
036import org.apache.hadoop.mapreduce.v2.jobhistory.JHAdminConfig;
037import org.apache.hadoop.security.Credentials;
038import org.apache.hadoop.security.token.Token;
039import org.apache.hadoop.net.NetUtils;
040import org.apache.hadoop.security.SecurityUtil;
041import org.apache.hadoop.security.UserGroupInformation;
042import org.apache.hadoop.security.token.TokenIdentifier;
043import org.apache.hadoop.yarn.api.records.LocalResource;
044import org.apache.hadoop.yarn.api.records.LocalResourceType;
045import org.apache.hadoop.yarn.api.records.LocalResourceVisibility;
046import org.apache.hadoop.yarn.client.ClientRMProxy;
047import org.apache.hadoop.yarn.client.api.YarnClient;
048import org.apache.hadoop.yarn.exceptions.YarnException;
049import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
050import org.apache.hadoop.yarn.ipc.YarnRPC;
051import org.apache.hadoop.yarn.util.ConverterUtils;
052import org.apache.hadoop.yarn.util.Records;
053import org.apache.oozie.ErrorCode;
054import org.apache.oozie.action.ActionExecutorException;
055import org.apache.oozie.action.hadoop.JavaActionExecutor;
056import org.apache.oozie.util.IOUtils;
057import org.apache.oozie.util.ParamChecker;
058import org.apache.oozie.util.XConfiguration;
059import org.apache.oozie.util.XLog;
060import org.apache.oozie.util.JobUtils;
061import org.apache.oozie.workflow.lite.LiteWorkflowAppParser;
062
063import java.io.File;
064import java.io.FileInputStream;
065import java.io.FilenameFilter;
066import java.io.IOException;
067import java.io.InputStream;
068import java.io.OutputStream;
069import java.lang.reflect.Method;
070import java.net.InetAddress;
071import java.net.URI;
072import java.net.URISyntaxException;
073import java.security.PrivilegedAction;
074import java.security.PrivilegedExceptionAction;
075import java.util.Arrays;
076import java.util.Comparator;
077import java.util.HashMap;
078import java.util.Map;
079import java.util.Properties;
080import java.util.Set;
081import java.util.HashSet;
082import java.util.concurrent.ConcurrentHashMap;
083
084
085/**
086 * The HadoopAccessorService returns HadoopAccessor instances configured to work on behalf of a user-group. <p> The
087 * default accessor used is the base accessor which just injects the UGI into the configuration instance used to
088 * create/obtain JobClient and FileSystem instances.
089 */
090public class HadoopAccessorService implements Service {
091
092    private static XLog LOG = XLog.getLog(HadoopAccessorService.class);
093
094    public static final String CONF_PREFIX = Service.CONF_PREFIX + "HadoopAccessorService.";
095    public static final String JOB_TRACKER_WHITELIST = CONF_PREFIX + "jobTracker.whitelist";
096    public static final String NAME_NODE_WHITELIST = CONF_PREFIX + "nameNode.whitelist";
097    public static final String HADOOP_CONFS = CONF_PREFIX + "hadoop.configurations";
098    public static final String ACTION_CONFS = CONF_PREFIX + "action.configurations";
099    public static final String ACTION_CONFS_LOAD_DEFAULT_RESOURCES = ACTION_CONFS + ".load.default.resources";
100    public static final String KERBEROS_AUTH_ENABLED = CONF_PREFIX + "kerberos.enabled";
101    public static final String KERBEROS_KEYTAB = CONF_PREFIX + "keytab.file";
102    public static final String KERBEROS_PRINCIPAL = CONF_PREFIX + "kerberos.principal";
103
104    private static final String OOZIE_HADOOP_ACCESSOR_SERVICE_CREATED = "oozie.HadoopAccessorService.created";
105    private static final String DEFAULT_ACTIONNAME = "default";
106    private static Configuration cachedConf;
107
108    private Set<String> jobTrackerWhitelist = new HashSet<String>();
109    private Set<String> nameNodeWhitelist = new HashSet<String>();
110    private Map<String, Configuration> hadoopConfigs = new HashMap<String, Configuration>();
111    private Map<String, File> actionConfigDirs = new HashMap<String, File>();
112    private Map<String, Map<String, XConfiguration>> actionConfigs = new HashMap<String, Map<String, XConfiguration>>();
113
114    private UserGroupInformationService ugiService;
115
116    /**
117     * Supported filesystem schemes for namespace federation
118     */
119    public static final String SUPPORTED_FILESYSTEMS = CONF_PREFIX + "supported.filesystems";
120    private Set<String> supportedSchemes;
121    private boolean allSchemesSupported;
122
123    public void init(Services services) throws ServiceException {
124        this.ugiService = services.get(UserGroupInformationService.class);
125        init(services.getConf());
126    }
127
128    //for testing purposes, see XFsTestCase
129    public void init(Configuration conf) throws ServiceException {
130        for (String name : ConfigurationService.getStrings(conf, JOB_TRACKER_WHITELIST)) {
131            String tmp = name.toLowerCase().trim();
132            if (tmp.length() == 0) {
133                continue;
134            }
135            jobTrackerWhitelist.add(tmp);
136        }
137        LOG.info(
138                "JOB_TRACKER_WHITELIST :" + jobTrackerWhitelist.toString()
139                        + ", Total entries :" + jobTrackerWhitelist.size());
140        for (String name : ConfigurationService.getStrings(conf, NAME_NODE_WHITELIST)) {
141            String tmp = name.toLowerCase().trim();
142            if (tmp.length() == 0) {
143                continue;
144            }
145            nameNodeWhitelist.add(tmp);
146        }
147        LOG.info(
148                "NAME_NODE_WHITELIST :" + nameNodeWhitelist.toString()
149                        + ", Total entries :" + nameNodeWhitelist.size());
150
151        boolean kerberosAuthOn = ConfigurationService.getBoolean(conf, KERBEROS_AUTH_ENABLED);
152        LOG.info("Oozie Kerberos Authentication [{0}]", (kerberosAuthOn) ? "enabled" : "disabled");
153        if (kerberosAuthOn) {
154            kerberosInit(conf);
155        }
156        else {
157            Configuration ugiConf = new Configuration();
158            ugiConf.set("hadoop.security.authentication", "simple");
159            UserGroupInformation.setConfiguration(ugiConf);
160        }
161
162        if (ugiService == null) { //for testing purposes, see XFsTestCase
163            this.ugiService = new UserGroupInformationService();
164        }
165
166        loadHadoopConfigs(conf);
167        preLoadActionConfigs(conf);
168
169        supportedSchemes = new HashSet<String>();
170        String[] schemesFromConf = ConfigurationService.getStrings(conf, SUPPORTED_FILESYSTEMS);
171        if(schemesFromConf != null) {
172            for (String scheme: schemesFromConf) {
173                scheme = scheme.trim();
174                // If user gives "*", supportedSchemes will be empty, so that checking is not done i.e. all schemes allowed
175                if(scheme.equals("*")) {
176                    if(schemesFromConf.length > 1) {
177                        throw new ServiceException(ErrorCode.E0100, getClass().getName(),
178                            SUPPORTED_FILESYSTEMS + " should contain either only wildcard or explicit list, not both");
179                    }
180                    allSchemesSupported = true;
181                }
182                supportedSchemes.add(scheme);
183            }
184        }
185
186        setConfigForHadoopSecurityUtil(conf);
187    }
188
189    private void setConfigForHadoopSecurityUtil(Configuration conf) {
190        // Prior to HADOOP-12954 (2.9.0+), Hadoop sets hadoop.security.token.service.use_ip on startup in a static block with no
191        // way for Oozie to change it because Oozie doesn't load *-site.xml files on the classpath.  HADOOP-12954 added a way to
192        // set this property via a setConfiguration method.  Ideally, this would be part of JobClient so Oozie wouldn't have to
193        // worry about it and we could have different values for different clusters, but we can't; so we have to use the same value
194        // for every cluster Oozie is configured for.  To that end, we'll use the default NN's configs.  If that's not defined,
195        // we'll use the wildcard's configs.  And if that's not defined, we'll use an arbitrary cluster's configs.  In any case,
196        // if the version of Hadoop we're using doesn't include HADOOP-12954, we'll do nothing (there's no workaround), and
197        // hadoop.security.token.service.use_ip will have the default value.
198        String nameNode = conf.get(LiteWorkflowAppParser.DEFAULT_NAME_NODE);
199        if (nameNode != null) {
200            nameNode = nameNode.trim();
201            if (nameNode.isEmpty()) {
202                nameNode = null;
203            }
204        }
205        if (nameNode == null && hadoopConfigs.containsKey("*")) {
206            nameNode = "*";
207        }
208        if (nameNode == null) {
209            for (String nn : hadoopConfigs.keySet()) {
210                nn = nn.trim();
211                if (!nn.isEmpty()) {
212                    nameNode = nn;
213                    break;
214                }
215            }
216        }
217        if (nameNode != null) {
218            Configuration hConf = getConfiguration(nameNode);
219            try {
220                Method setConfigurationMethod = SecurityUtil.class.getMethod("setConfiguration", Configuration.class);
221                setConfigurationMethod.invoke(null, hConf);
222                LOG.debug("Setting Hadoop SecurityUtil Configuration to that of {0}", nameNode);
223            } catch (NoSuchMethodException e) {
224                LOG.debug("Not setting Hadoop SecurityUtil Configuration because this version of Hadoop doesn't support it");
225            } catch (Exception e) {
226                LOG.error("An Exception occurred while trying to call setConfiguration on {0} via Reflection.  It won't be called.",
227                        SecurityUtil.class.getName(), e);
228            }
229        }
230    }
231
232    private void kerberosInit(Configuration serviceConf) throws ServiceException {
233            try {
234                String keytabFile = ConfigurationService.get(serviceConf, KERBEROS_KEYTAB).trim();
235                if (keytabFile.length() == 0) {
236                    throw new ServiceException(ErrorCode.E0026, KERBEROS_KEYTAB);
237                }
238                String principal = SecurityUtil.getServerPrincipal(
239                        serviceConf.get(KERBEROS_PRINCIPAL, "oozie/localhost@LOCALHOST"),
240                        InetAddress.getLocalHost().getCanonicalHostName());
241                if (principal.length() == 0) {
242                    throw new ServiceException(ErrorCode.E0026, KERBEROS_PRINCIPAL);
243                }
244                Configuration conf = new Configuration();
245                conf.set("hadoop.security.authentication", "kerberos");
246                UserGroupInformation.setConfiguration(conf);
247                UserGroupInformation.loginUserFromKeytab(principal, keytabFile);
248                LOG.info("Got Kerberos ticket, keytab [{0}], Oozie principal principal [{1}]",
249                        keytabFile, principal);
250            }
251            catch (ServiceException ex) {
252                throw ex;
253            }
254            catch (Exception ex) {
255                throw new ServiceException(ErrorCode.E0100, getClass().getName(), ex.getMessage(), ex);
256            }
257    }
258
259    private static final String[] HADOOP_CONF_FILES =
260        {"core-site.xml", "hdfs-site.xml", "mapred-site.xml", "yarn-site.xml", "hadoop-site.xml", "ssl-client.xml"};
261
262
263    private Configuration loadHadoopConf(File dir) throws IOException {
264        Configuration hadoopConf = new XConfiguration();
265        for (String file : HADOOP_CONF_FILES) {
266            File f = new File(dir, file);
267            if (f.exists()) {
268                InputStream is = new FileInputStream(f);
269                Configuration conf = new XConfiguration(is, false);
270                is.close();
271                XConfiguration.copy(conf, hadoopConf);
272            }
273        }
274        return hadoopConf;
275    }
276
277    private Map<String, File> parseConfigDirs(String[] confDefs, String type) throws ServiceException, IOException {
278        Map<String, File> map = new HashMap<String, File>();
279        File configDir = new File(ConfigurationService.getConfigurationDirectory());
280        for (String confDef : confDefs) {
281            if (confDef.trim().length() > 0) {
282                String[] parts = confDef.split("=");
283                if (parts.length == 2) {
284                    String hostPort = parts[0];
285                    String confDir = parts[1];
286                    File dir = new File(confDir);
287                    if (!dir.isAbsolute()) {
288                        dir = new File(configDir, confDir);
289                    }
290                    if (dir.exists()) {
291                        map.put(hostPort.toLowerCase(), dir);
292                    }
293                    else {
294                        throw new ServiceException(ErrorCode.E0100, getClass().getName(),
295                                                   "could not find " + type + " configuration directory: " +
296                                                   dir.getAbsolutePath());
297                    }
298                }
299                else {
300                    throw new ServiceException(ErrorCode.E0100, getClass().getName(),
301                                               "Incorrect " + type + " configuration definition: " + confDef);
302                }
303            }
304        }
305        return map;
306    }
307
308    private void loadHadoopConfigs(Configuration serviceConf) throws ServiceException {
309        try {
310            Map<String, File> map = parseConfigDirs(ConfigurationService.getStrings(serviceConf, HADOOP_CONFS),
311                    "hadoop");
312            for (Map.Entry<String, File> entry : map.entrySet()) {
313                hadoopConfigs.put(entry.getKey(), loadHadoopConf(entry.getValue()));
314            }
315        }
316        catch (ServiceException ex) {
317            throw ex;
318        }
319        catch (Exception ex) {
320            throw new ServiceException(ErrorCode.E0100, getClass().getName(), ex.getMessage(), ex);
321        }
322    }
323
324    private void preLoadActionConfigs(Configuration serviceConf) throws ServiceException {
325        try {
326            actionConfigDirs = parseConfigDirs(ConfigurationService.getStrings(serviceConf, ACTION_CONFS), "action");
327            for (String hostport : actionConfigDirs.keySet()) {
328                actionConfigs.put(hostport, new ConcurrentHashMap<String, XConfiguration>());
329            }
330        }
331        catch (ServiceException ex) {
332            throw ex;
333        }
334        catch (Exception ex) {
335            throw new ServiceException(ErrorCode.E0100, getClass().getName(), ex.getMessage(), ex);
336        }
337    }
338
339    public void destroy() {
340    }
341
342    public Class<? extends Service> getInterface() {
343        return HadoopAccessorService.class;
344    }
345
346    UserGroupInformation getUGI(String user) throws IOException {
347        return ugiService.getProxyUser(user);
348    }
349
350    /**
351     * Creates a Configuration using the site configuration for the specified hostname:port.
352     * <p>
353     * If the specified hostname:port is not defined it falls back to the '*' site
354     * configuration if available. If the '*' site configuration is not available,
355     * the JobConf has all Hadoop defaults.
356     *
357     * @param hostPort hostname:port to lookup Hadoop site configuration.
358     * @return a Configuration with the corresponding site configuration for hostPort.
359     */
360    public Configuration createConfiguration(String hostPort) {
361        Configuration appConf = new Configuration(getCachedConf());
362        XConfiguration.copy(getConfiguration(hostPort), appConf);
363        appConf.setBoolean(OOZIE_HADOOP_ACCESSOR_SERVICE_CREATED, true);
364        return appConf;
365    }
366
367    public Configuration getCachedConf() {
368        if (cachedConf == null) {
369            loadCachedConf();
370        }
371        return cachedConf;
372    }
373
374    private void loadCachedConf() {
375        cachedConf = new Configuration();
376        //for lazy loading
377        cachedConf.size();
378    }
379
380    private XConfiguration loadActionConf(String hostPort, String action) {
381        File dir = actionConfigDirs.get(hostPort);
382        XConfiguration actionConf = new XConfiguration();
383        if (dir != null) {
384            // See if a dir with the action name exists.   If so, load all the supported conf files in the dir
385            File actionConfDir = new File(dir, action);
386
387            if (actionConfDir.exists() && actionConfDir.isDirectory()) {
388                LOG.info("Processing configuration files under [{0}]"
389                                + " for action [{1}] and hostPort [{2}]",
390                        actionConfDir.getAbsolutePath(), action, hostPort);
391                updateActionConfigWithDir(actionConf, actionConfDir);
392            }
393        }
394
395        // Now check for <action.xml>   This way <action.xml> has priority over <action-dir>/*.xml
396        File actionConfFile = new File(dir, action + ".xml");
397        LOG.info("Processing configuration file [{0}] for action [{1}] and hostPort [{2}]",
398            actionConfFile.getAbsolutePath(), action, hostPort);
399        if (actionConfFile.exists()) {
400            updateActionConfigWithFile(actionConf, actionConfFile);
401        }
402
403        return actionConf;
404    }
405
406    private void updateActionConfigWithFile(Configuration actionConf, File actionConfFile)  {
407        try {
408            Configuration conf = readActionConfFile(actionConfFile);
409            XConfiguration.copy(conf, actionConf);
410        } catch (IOException e) {
411            LOG.warn("Could not read file [{0}].", actionConfFile.getAbsolutePath());
412        }
413    }
414
415    private void updateActionConfigWithDir(Configuration actionConf, File actionConfDir) {
416        File[] actionConfFiles = actionConfDir.listFiles(new FilenameFilter() {
417            @Override
418            public boolean accept(File dir, String name) {
419                return ActionConfFileType.isSupportedFileType(name);
420            }});
421
422        if (actionConfFiles != null) {
423            Arrays.sort(actionConfFiles, new Comparator<File>() {
424                @Override
425                public int compare(File o1, File o2) {
426                    return o1.getName().compareTo(o2.getName());
427                }
428            });
429            for (File f : actionConfFiles) {
430                if (f.isFile() && f.canRead()) {
431                    updateActionConfigWithFile(actionConf, f);
432                }
433            }
434        }
435    }
436
437    private Configuration readActionConfFile(File file) throws IOException {
438        InputStream fis = null;
439        try {
440            fis = new FileInputStream(file);
441            ActionConfFileType fileTyple = ActionConfFileType.getFileType(file.getName());
442            switch (fileTyple) {
443                case XML:
444                    return new XConfiguration(fis);
445                case PROPERTIES:
446                    Properties properties = new Properties();
447                    properties.load(fis);
448                    return new XConfiguration(properties);
449                default:
450                    throw new UnsupportedOperationException(
451                        String.format("Unable to parse action conf file of type %s", fileTyple));
452            }
453        } finally {
454            IOUtils.closeSafely(fis);
455        }
456    }
457
458    /**
459     * Returns a Configuration containing any defaults for an action for a particular cluster.
460     * <p>
461     * This configuration is used as default for the action configuration and enables cluster
462     * level default values per action.
463     *
464     * @param hostPort hostname"port to lookup the action default confiugration.
465     * @param action action name.
466     * @return the default configuration for the action for the specified cluster.
467     */
468    public XConfiguration createActionDefaultConf(String hostPort, String action) {
469        hostPort = (hostPort != null) ? hostPort.toLowerCase() : null;
470        Map<String, XConfiguration> hostPortActionConfigs = actionConfigs.get(hostPort);
471        if (hostPortActionConfigs == null) {
472            hostPortActionConfigs = actionConfigs.get("*");
473            hostPort = "*";
474        }
475        XConfiguration actionConf = hostPortActionConfigs.get(action);
476        if (actionConf == null) {
477            // doing lazy loading as we don't know upfront all actions, no need to synchronize
478            // as it is a read operation an in case of a race condition loading and inserting
479            // into the Map is idempotent and the action-config Map is a ConcurrentHashMap
480
481            // We first load a action of type default
482            // This allows for global configuration for all actions - for example
483            // all launchers in one queue and actions in another queue
484            // Are some configuration that applies to multiple actions - like
485            // config libraries path etc
486            actionConf = loadActionConf(hostPort, DEFAULT_ACTIONNAME);
487
488            // Action specific default configuration will override the default action config
489
490            XConfiguration.copy(loadActionConf(hostPort, action), actionConf);
491            hostPortActionConfigs.put(action, actionConf);
492        }
493        return new XConfiguration(actionConf.toProperties());
494    }
495
496    private Configuration getConfiguration(String hostPort) {
497        hostPort = (hostPort != null) ? hostPort.toLowerCase() : null;
498        Configuration conf = hadoopConfigs.get(hostPort);
499        if (conf == null) {
500            conf = hadoopConfigs.get("*");
501            if (conf == null) {
502                conf = new XConfiguration();
503            }
504        }
505        return conf;
506    }
507
508    /**
509     * Return a JobClient created with the provided user/group.
510     *
511     *
512     * @param conf JobConf with all necessary information to create the
513     *        JobClient.
514     * @return JobClient created with the provided user/group.
515     * @throws HadoopAccessorException if the client could not be created.
516     */
517    public JobClient createJobClient(String user, final JobConf conf) throws HadoopAccessorException {
518        ParamChecker.notEmpty(user, "user");
519        if (!conf.getBoolean(OOZIE_HADOOP_ACCESSOR_SERVICE_CREATED, false)) {
520            throw new HadoopAccessorException(ErrorCode.E0903);
521        }
522        String jobTracker = conf.get(JavaActionExecutor.HADOOP_YARN_RM);
523        validateJobTracker(jobTracker);
524        try {
525            UserGroupInformation ugi = getUGI(user);
526            JobClient jobClient = ugi.doAs(new PrivilegedExceptionAction<JobClient>() {
527                public JobClient run() throws Exception {
528                    return new JobClient(conf);
529                }
530            });
531            return jobClient;
532        }
533        catch (IOException | InterruptedException ex) {
534            throw new HadoopAccessorException(ErrorCode.E0902, ex.getMessage(), ex);
535        }
536    }
537
538    /**
539     * Return a JobClient created with the provided user/group.
540     *
541     *
542     * @param conf Configuration with all necessary information to create the
543     *        JobClient.
544     * @return JobClient created with the provided user/group.
545     * @throws HadoopAccessorException if the client could not be created.
546     */
547    public JobClient createJobClient(String user, Configuration conf) throws HadoopAccessorException {
548        return createJobClient(user, new JobConf(conf));
549    }
550
551    /**
552     * Return a YarnClient created with the provided user and configuration. The caller is responsible for closing it when done.
553     *
554     * @param user The username to impersonate
555     * @param conf The conf
556     * @return a YarnClient with the provided user and configuration
557     * @throws HadoopAccessorException if the client could not be created.
558     */
559    public YarnClient createYarnClient(String user, final Configuration conf) throws HadoopAccessorException {
560        ParamChecker.notEmpty(user, "user");
561        if (!conf.getBoolean(OOZIE_HADOOP_ACCESSOR_SERVICE_CREATED, false)) {
562            throw new HadoopAccessorException(ErrorCode.E0903);
563        }
564        String rm = conf.get(JavaActionExecutor.HADOOP_YARN_RM);
565        validateJobTracker(rm);
566        try {
567            UserGroupInformation ugi = getUGI(user);
568            YarnClient yarnClient = ugi.doAs(new PrivilegedExceptionAction<YarnClient>() {
569                @Override
570                public YarnClient run() throws Exception {
571                    YarnClient yarnClient = YarnClient.createYarnClient();
572                    yarnClient.init(conf);
573                    yarnClient.start();
574                    return yarnClient;
575                }
576            });
577            return yarnClient;
578        } catch (IOException | InterruptedException ex) {
579            throw new HadoopAccessorException(ErrorCode.E0902, ex.getMessage(), ex);
580        }
581    }
582
583    /**
584     * Return a FileSystem created with the provided user for the specified URI.
585     *
586     * @param user The username to impersonate
587     * @param uri file system URI.
588     * @param conf Configuration with all necessary information to create the FileSystem.
589     * @return FileSystem created with the provided user/group.
590     * @throws HadoopAccessorException if the filesystem could not be created.
591     */
592    public FileSystem createFileSystem(String user, final URI uri, final Configuration conf)
593            throws HadoopAccessorException {
594       return createFileSystem(user, uri, conf, true);
595    }
596
597    private FileSystem createFileSystem(String user, final URI uri, final Configuration conf, boolean checkAccessorProperty)
598            throws HadoopAccessorException {
599        ParamChecker.notEmpty(user, "user");
600
601        if (checkAccessorProperty && !conf.getBoolean(OOZIE_HADOOP_ACCESSOR_SERVICE_CREATED, false)) {
602            throw new HadoopAccessorException(ErrorCode.E0903);
603        }
604
605        checkSupportedFilesystem(uri);
606
607        String nameNode = uri.getAuthority();
608        if (nameNode == null) {
609            nameNode = conf.get("fs.default.name");
610            if (nameNode != null) {
611                try {
612                    nameNode = new URI(nameNode).getAuthority();
613                }
614                catch (URISyntaxException ex) {
615                    throw new HadoopAccessorException(ErrorCode.E0902, ex.getMessage(), ex);
616                }
617            }
618        }
619        validateNameNode(nameNode);
620
621        try {
622            UserGroupInformation ugi = getUGI(user);
623            return ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {
624                public FileSystem run() throws Exception {
625                    return FileSystem.get(uri, conf);
626                }
627            });
628        }
629        catch (IOException | InterruptedException ex) {
630            throw new HadoopAccessorException(ErrorCode.E0902, ex.getMessage(), ex);
631        }
632    }
633
634    /**
635     * Validate Job tracker
636     * @param jobTrackerUri
637     * @throws HadoopAccessorException
638     */
639    protected void validateJobTracker(String jobTrackerUri) throws HadoopAccessorException {
640        validate(jobTrackerUri, jobTrackerWhitelist, ErrorCode.E0900);
641    }
642
643    /**
644     * Validate Namenode list
645     * @param nameNodeUri
646     * @throws HadoopAccessorException
647     */
648    protected void validateNameNode(String nameNodeUri) throws HadoopAccessorException {
649        validate(nameNodeUri, nameNodeWhitelist, ErrorCode.E0901);
650    }
651
652    private void validate(String uri, Set<String> whitelist, ErrorCode error) throws HadoopAccessorException {
653        if (uri != null) {
654            uri = uri.toLowerCase().trim();
655            if (whitelist.size() > 0 && !whitelist.contains(uri)) {
656                throw new HadoopAccessorException(error, uri, whitelist);
657            }
658        }
659    }
660
661    public void addFileToClassPath(String user, final Path file, final Configuration conf)
662            throws IOException {
663        ParamChecker.notEmpty(user, "user");
664        try {
665            UserGroupInformation ugi = getUGI(user);
666            ugi.doAs(new PrivilegedExceptionAction<Void>() {
667                @Override
668                public Void run() throws Exception {
669                    JobUtils.addFileToClassPath(file, conf, null);
670                    return null;
671                }
672            });
673
674        }
675        catch (InterruptedException ex) {
676            throw new IOException(ex);
677        }
678
679    }
680
681    /**
682     * checks configuration parameter if filesystem scheme is among the list of supported ones
683     * this makes system robust to filesystems other than HDFS also
684     */
685
686    public void checkSupportedFilesystem(URI uri) throws HadoopAccessorException {
687        if (allSchemesSupported)
688            return;
689        String uriScheme = uri.getScheme();
690        if (uriScheme != null) {    // skip the check if no scheme is given
691            if(!supportedSchemes.isEmpty()) {
692                LOG.debug("Checking if filesystem " + uriScheme + " is supported");
693                if (!supportedSchemes.contains(uriScheme)) {
694                    throw new HadoopAccessorException(ErrorCode.E0904, uriScheme, uri.toString());
695                }
696             }
697         }
698    }
699
700    public Set<String> getSupportedSchemes() {
701        return supportedSchemes;
702    }
703
704    /**
705     * Creates a {@link LocalResource} for the Configuration to localize it for a Yarn Container.  This involves also writing it
706     * to HDFS.
707     * Example usage:
708     * * <pre>
709     * {@code
710     * LocalResource res1 = createLocalResourceForConfigurationFile(filename1, user, conf, uri, dir);
711     * LocalResource res2 = createLocalResourceForConfigurationFile(filename2, user, conf, uri, dir);
712     * ...
713     * Map<String, LocalResource> localResources = new HashMap<String, LocalResource>();
714     * localResources.put(filename1, res1);
715     * localResources.put(filename2, res2);
716     * ...
717     * containerLaunchContext.setLocalResources(localResources);
718     * }
719     * </pre>
720     *
721     * @param filename The filename to use on the remote filesystem and once it has been localized.
722     * @param user The user
723     * @param conf The configuration to process
724     * @param uri The URI of the remote filesystem (e.g. HDFS)
725     * @param dir The directory on the remote filesystem to write the file to
726     * @return localResource
727     * @throws IOException A problem occurred writing the file
728     * @throws HadoopAccessorException A problem occured with Hadoop
729     * @throws URISyntaxException A problem occurred parsing the URI
730     */
731    public LocalResource createLocalResourceForConfigurationFile(String filename, String user, Configuration conf, URI uri,
732                                                                 Path dir)
733            throws IOException, HadoopAccessorException, URISyntaxException {
734        Path dst = new Path(dir, filename);
735        FileSystem fs = createFileSystem(user, uri, conf, false);
736        try (OutputStream os = fs.create(dst)){
737            conf.writeXml(os);
738        }
739        LocalResource localResource = Records.newRecord(LocalResource.class);
740        localResource.setType(LocalResourceType.FILE); localResource.setVisibility(LocalResourceVisibility.APPLICATION);
741        localResource.setResource(ConverterUtils.getYarnUrlFromPath(dst));
742        FileStatus destStatus = fs.getFileStatus(dst);
743        localResource.setTimestamp(destStatus.getModificationTime());
744        localResource.setSize(destStatus.getLen());
745        return localResource;
746    }
747
748}