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}