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 java.io.File; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URL; 029import java.net.URLDecoder; 030import java.text.MessageFormat; 031import java.text.ParseException; 032import java.text.SimpleDateFormat; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.Calendar; 036import java.util.Comparator; 037import java.util.Date; 038import java.util.Enumeration; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Map; 043import java.util.Properties; 044import java.util.Set; 045import java.util.TimeZone; 046import java.util.Map.Entry; 047import org.apache.commons.lang.StringUtils; 048import org.apache.hadoop.conf.Configuration; 049import org.apache.hadoop.fs.FileStatus; 050import org.apache.hadoop.fs.FileSystem; 051import org.apache.hadoop.fs.LocalFileSystem; 052import org.apache.hadoop.fs.Path; 053import org.apache.hadoop.fs.PathFilter; 054import org.apache.hadoop.fs.permission.FsPermission; 055import org.apache.hadoop.io.IOUtils; 056import org.apache.oozie.action.ActionExecutor; 057import org.apache.oozie.action.hadoop.JavaActionExecutor; 058import org.apache.oozie.client.rest.JsonUtils; 059import com.google.common.annotations.VisibleForTesting; 060import org.apache.oozie.ErrorCode; 061import org.apache.oozie.util.Instrumentable; 062import org.apache.oozie.util.Instrumentation; 063import org.apache.oozie.util.FSUtils; 064import org.apache.oozie.util.XConfiguration; 065import org.apache.oozie.util.XLog; 066import org.jdom.JDOMException; 067 068import static org.apache.oozie.util.FSUtils.isLocalFile; 069 070public class ShareLibService implements Service, Instrumentable { 071 072 public static final String LAUNCHERJAR_LIB_RETENTION = CONF_PREFIX + "ShareLibService.temp.sharelib.retention.days"; 073 074 public static final String SHARELIB_MAPPING_FILE = CONF_PREFIX + "ShareLibService.mapping.file"; 075 076 public static final String SHIP_LAUNCHER_JAR = "oozie.action.ship.launcher.jar"; 077 078 public static final String PURGE_INTERVAL = CONF_PREFIX + "ShareLibService.purge.interval"; 079 080 public static final String FAIL_FAST_ON_STARTUP = CONF_PREFIX + "ShareLibService.fail.fast.on.startup"; 081 082 private static final String PERMISSION_STRING = "-rwxr-xr-x"; 083 084 public static final String LAUNCHER_LIB_PREFIX = "launcher_"; 085 086 public static final String SHARE_LIB_PREFIX = "lib_"; 087 088 public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); 089 090 private Services services; 091 092 private Map<String, List<Path>> shareLibMap = new HashMap<String, List<Path>>(); 093 094 private Map<String, Map<Path, Configuration>> shareLibConfigMap = new HashMap<String, Map<Path, Configuration>>(); 095 096 private Map<String, List<Path>> launcherLibMap = new HashMap<String, List<Path>>(); 097 098 private Set<String> actionConfSet = new HashSet<String>(); 099 100 // symlink mapping. Oozie keeps on checking symlink path and if changes, Oozie reloads the sharelib 101 private Map<String, Map<Path, Path>> symlinkMapping = new HashMap<String, Map<Path, Path>>(); 102 103 private static XLog LOG = XLog.getLog(ShareLibService.class); 104 105 private String sharelibMappingFile; 106 107 private boolean isShipLauncherEnabled = false; 108 109 public static String SHARE_LIB_CONF_PREFIX = "oozie"; 110 111 private boolean shareLibLoadAttempted = false; 112 113 private String sharelibMetaFileOldTimeStamp; 114 115 private String sharelibDirOld; 116 117 FileSystem fs; 118 FileSystem localFs; 119 120 final long retentionTime = 1000L * 60 * 60 * 24 * ConfigurationService.getInt(LAUNCHERJAR_LIB_RETENTION); 121 122 @Override 123 public void init(Services services) throws ServiceException { 124 this.services = services; 125 sharelibMappingFile = ConfigurationService.get(services.getConf(), SHARELIB_MAPPING_FILE); 126 isShipLauncherEnabled = ConfigurationService.getBoolean(services.getConf(), SHIP_LAUNCHER_JAR); 127 boolean failOnfailure = ConfigurationService.getBoolean(services.getConf(), FAIL_FAST_ON_STARTUP); 128 Path launcherlibPath = getLauncherlibPath(); 129 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 130 URI uri = launcherlibPath.toUri(); 131 try { 132 133 fs = FileSystem.get(has.createConfiguration(uri.getAuthority())); 134 localFs = LocalFileSystem.get(new Configuration(false)); 135 //cache action key sharelib conf list 136 cacheActionKeySharelibConfList(); 137 updateLauncherLib(); 138 updateShareLib(); 139 } 140 catch (Throwable e) { 141 if (failOnfailure) { 142 LOG.error("Sharelib initialization fails", e); 143 throw new ServiceException(ErrorCode.E0104, getClass().getName(), "Sharelib initialization fails. ", e); 144 } 145 else { 146 // We don't want to actually fail init by throwing an Exception, so only create the ServiceException and 147 // log it 148 ServiceException se = new ServiceException(ErrorCode.E0104, getClass().getName(), 149 "Not able to cache sharelib. An Admin needs to install the sharelib with oozie-setup.sh and issue the " 150 + "'oozie admin' CLI command to update the sharelib", e); 151 LOG.error(se); 152 } 153 } 154 Runnable purgeLibsRunnable = new Runnable() { 155 @Override 156 public void run() { 157 System.out.flush(); 158 try { 159 // Only one server should purge sharelib 160 if (Services.get().get(JobsConcurrencyService.class).isLeader()) { 161 final Date current = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime(); 162 purgeLibs(fs, LAUNCHER_LIB_PREFIX, current); 163 purgeLibs(fs, SHARE_LIB_PREFIX, current); 164 } 165 } 166 catch (IOException e) { 167 LOG.error("There was an issue purging the sharelib", e); 168 } 169 } 170 }; 171 services.get(SchedulerService.class).schedule(purgeLibsRunnable, 10, 172 ConfigurationService.getInt(services.getConf(), PURGE_INTERVAL) * 60 * 60 * 24, 173 SchedulerService.Unit.SEC); 174 } 175 176 /** 177 * Recursively change permissions. 178 * 179 * @throws IOException Signals that an I/O exception has occurred. 180 */ 181 private void updateLauncherLib() throws IOException { 182 if (isShipLauncherEnabled) { 183 if (fs == null) { 184 Path launcherlibPath = getLauncherlibPath(); 185 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 186 URI uri = launcherlibPath.toUri(); 187 fs = FileSystem.get(has.createConfiguration(uri.getAuthority())); 188 } 189 Path launcherlibPath = getLauncherlibPath(); 190 setupLauncherLibPath(fs, launcherlibPath); 191 recursiveChangePermissions(fs, launcherlibPath, FsPermission.valueOf(PERMISSION_STRING)); 192 } 193 194 } 195 196 /** 197 * Copy launcher jars to Temp directory. 198 * 199 * @param fs the FileSystem 200 * @param tmpLauncherLibPath the tmp launcher lib path 201 * @throws IOException Signals that an I/O exception has occurred. 202 */ 203 private void setupLauncherLibPath(FileSystem fs, Path tmpLauncherLibPath) throws IOException { 204 205 ActionService actionService = Services.get().get(ActionService.class); 206 List<Class<?>> classes = JavaActionExecutor.getCommonLauncherClasses(); 207 Path baseDir = new Path(tmpLauncherLibPath, JavaActionExecutor.OOZIE_COMMON_LIBDIR); 208 copyJarContainingClasses(classes, fs, baseDir, JavaActionExecutor.OOZIE_COMMON_LIBDIR); 209 Set<String> actionTypes = actionService.getActionTypes(); 210 for (String key : actionTypes) { 211 ActionExecutor executor = actionService.getExecutor(key); 212 if (executor instanceof JavaActionExecutor) { 213 JavaActionExecutor jexecutor = (JavaActionExecutor) executor; 214 classes = jexecutor.getLauncherClasses(); 215 if (classes != null) { 216 String type = executor.getType(); 217 Path executorDir = new Path(tmpLauncherLibPath, type); 218 copyJarContainingClasses(classes, fs, executorDir, type); 219 } 220 } 221 } 222 } 223 224 /** 225 * Recursive change permissions. 226 * 227 * @param fs the FileSystem 228 * @param path the Path 229 * @param fsPerm is permission 230 * @throws IOException Signals that an I/O exception has occurred. 231 */ 232 private void recursiveChangePermissions(FileSystem fs, Path path, FsPermission fsPerm) throws IOException { 233 fs.setPermission(path, fsPerm); 234 FileStatus[] filesStatus = fs.listStatus(path); 235 for (int i = 0; i < filesStatus.length; i++) { 236 Path p = filesStatus[i].getPath(); 237 if (filesStatus[i].isDirectory()) { 238 recursiveChangePermissions(fs, p, fsPerm); 239 } 240 else { 241 fs.setPermission(p, fsPerm); 242 } 243 } 244 } 245 246 /** 247 * Copy jar containing classes. 248 * 249 * @param classes the classes 250 * @param fs the FileSystem 251 * @param executorDir is Path 252 * @param type is sharelib key 253 * @throws IOException Signals that an I/O exception has occurred. 254 */ 255 private void copyJarContainingClasses(List<Class<?>> classes, FileSystem fs, Path executorDir, String type) 256 throws IOException { 257 fs.mkdirs(executorDir); 258 Set<String> localJarSet = new HashSet<String>(); 259 for (Class<?> c : classes) { 260 String localJar = findContainingJar(c); 261 if (localJar != null) { 262 localJarSet.add(localJar); 263 } 264 else { 265 throw new IOException("No jar containing " + c + " found"); 266 } 267 } 268 List<Path> listOfPaths = new ArrayList<Path>(); 269 for (String localJarStr : localJarSet) { 270 File localJar = new File(localJarStr); 271 copyFromLocalFile(localJar, fs, executorDir); 272 Path path = new Path(executorDir, localJar.getName()); 273 listOfPaths.add(path); 274 LOG.info(localJar.getName() + " uploaded to " + executorDir.toString()); 275 } 276 launcherLibMap.put(type, listOfPaths); 277 278 } 279 280 private static boolean copyFromLocalFile(File src, FileSystem dstFS, Path dstDir) throws IOException { 281 Path dst = new Path(dstDir, src.getName()); 282 InputStream in=null; 283 OutputStream out = null; 284 try { 285 in = new FileInputStream(src); 286 out = dstFS.create(dst, true); 287 IOUtils.copyBytes(in, out, dstFS.getConf(), true); 288 } catch (IOException e) { 289 IOUtils.closeStream(out); 290 IOUtils.closeStream(in); 291 throw e; 292 } 293 return true; 294 295 } 296 297 /** 298 * Gets the path recursively. 299 * 300 * @param fs the FileSystem 301 * @param rootDir the root directory 302 * @param listOfPaths the list of paths 303 * @param shareLibKey the share lib key 304 * @return the path recursively 305 * @throws IOException Signals that an I/O exception has occurred. 306 */ 307 private void getPathRecursively(FileSystem fs, Path rootDir, List<Path> listOfPaths, String shareLibKey, 308 Map<String, Map<Path, Configuration>> shareLibConfigMap) throws IOException { 309 if (rootDir == null) { 310 return; 311 } 312 313 try { 314 if (fs.isFile(new Path(new URI(rootDir.toString()).getPath()))) { 315 Path filePath = new Path(new URI(rootDir.toString()).getPath()); 316 Path qualifiedRootDirPath = fs.makeQualified(rootDir); 317 if (isFilePartOfConfList(rootDir)) { 318 cachePropertyFile(qualifiedRootDirPath, filePath, shareLibKey, shareLibConfigMap); 319 } 320 listOfPaths.add(qualifiedRootDirPath); 321 return; 322 } 323 324 FileStatus[] status = fs.listStatus(rootDir); 325 if (status == null) { 326 LOG.info("Shared lib " + rootDir + " doesn't exist, not adding to cache"); 327 return; 328 } 329 330 for (FileStatus file : status) { 331 if (file.isDirectory()) { 332 getPathRecursively(fs, file.getPath(), listOfPaths, shareLibKey, shareLibConfigMap); 333 } 334 else { 335 if (isFilePartOfConfList(file.getPath())) { 336 cachePropertyFile(file.getPath(), file.getPath(), shareLibKey, shareLibConfigMap); 337 } 338 listOfPaths.add(file.getPath()); 339 } 340 } 341 } 342 catch (URISyntaxException e) { 343 throw new IOException(e); 344 } 345 catch (JDOMException e) { 346 throw new IOException(e); 347 } 348 } 349 350 public Map<String, List<Path>> getShareLib() { 351 return shareLibMap; 352 } 353 354 private Map<String, Map<Path, Path>> getSymlinkMapping() { 355 return symlinkMapping; 356 } 357 358 /** 359 * Gets the action sharelib lib jars. 360 * 361 * @param shareLibKey the sharelib key 362 * @return List of paths 363 * @throws IOException Signals that an I/O exception has occurred. 364 */ 365 public List<Path> getShareLibJars(String shareLibKey) throws IOException { 366 // Sharelib map is empty means that on previous or startup attempt of 367 // caching sharelib has failed.Trying to reload 368 if (shareLibMap.isEmpty() && !shareLibLoadAttempted) { 369 synchronized (ShareLibService.class) { 370 if (shareLibMap.isEmpty()) { 371 updateShareLib(); 372 shareLibLoadAttempted = true; 373 } 374 } 375 } 376 checkSymlink(shareLibKey); 377 return shareLibMap.get(shareLibKey); 378 } 379 380 private void checkSymlink(final String shareLibKey) throws IOException { 381 if (symlinkMapping.get(shareLibKey) == null || symlinkMapping.get(shareLibKey).isEmpty()) { 382 return; 383 } 384 385 for (final Path symlinkPath : symlinkMapping.get(shareLibKey).keySet()) { 386 final FileSystem fileSystem = getHostFileSystem(symlinkPath); 387 final Path symLinkTarget = FSUtils.getSymLinkTarget(fileSystem, symlinkPath); 388 final boolean symlinkIsNotTarget = !getSymlinkSharelibPath(shareLibKey, symlinkPath).equals(symLinkTarget); 389 if (symlinkIsNotTarget) { 390 synchronized (ShareLibService.class) { 391 final Map<String, List<Path>> tmpShareLibMap = new HashMap<String, List<Path>>(shareLibMap); 392 393 final Map<String, Map<Path, Configuration>> tmpShareLibConfigMap = new HashMap<>(shareLibConfigMap); 394 395 final Map<String, Map<Path, Path>> tmpSymlinkMapping = new HashMap<String, Map<Path, Path>>( 396 symlinkMapping); 397 398 LOG.info(MessageFormat.format("Symlink target for [{0}] has changed, was [{1}], now [{2}]", 399 shareLibKey, symlinkPath, symLinkTarget)); 400 loadShareLibMetaFile(tmpShareLibMap, tmpSymlinkMapping, tmpShareLibConfigMap, sharelibMappingFile, 401 shareLibKey); 402 shareLibMap = tmpShareLibMap; 403 symlinkMapping = tmpSymlinkMapping; 404 shareLibConfigMap = tmpShareLibConfigMap; 405 return; 406 } 407 } 408 } 409 } 410 411 private Path getSymlinkSharelibPath(String shareLibKey, Path path) { 412 return symlinkMapping.get(shareLibKey).get(path); 413 } 414 415 private FileSystem getHostFileSystem(String pathStr) { 416 FileSystem fileSystem; 417 if (isLocalFile(pathStr)) { 418 fileSystem = localFs; 419 } 420 else { 421 fileSystem = fs; 422 } 423 return fileSystem; 424 } 425 426 private FileSystem getHostFileSystem(Path path) { 427 return getHostFileSystem(path.toString()); 428 } 429 430 /** 431 * Gets the launcher jars. 432 * 433 * @param shareLibKey the shareLib key 434 * @return launcher jars paths 435 * @throws IOException Signals that an I/O exception has occurred. 436 */ 437 public List<Path> getSystemLibJars(String shareLibKey) throws IOException { 438 List<Path> returnList = new ArrayList<Path>(); 439 // Sharelib map is empty means that on previous or startup attempt of 440 // caching launcher jars has failed.Trying to reload 441 if (isShipLauncherEnabled) { 442 if (launcherLibMap.isEmpty()) { 443 synchronized (ShareLibService.class) { 444 if (launcherLibMap.isEmpty()) { 445 updateLauncherLib(); 446 } 447 } 448 } 449 if (launcherLibMap.get(shareLibKey) != null) { 450 returnList.addAll(launcherLibMap.get(shareLibKey)); 451 } 452 } 453 if (shareLibKey.equals(JavaActionExecutor.OOZIE_COMMON_LIBDIR)) { 454 List<Path> sharelibList = getShareLibJars(shareLibKey); 455 if (sharelibList != null) { 456 returnList.addAll(sharelibList); 457 } 458 } 459 return returnList; 460 } 461 462 /** 463 * Find containing jar containing. 464 * 465 * @param clazz the clazz 466 * @return the string 467 */ 468 @VisibleForTesting 469 protected String findContainingJar(Class<?> clazz) { 470 ClassLoader loader = clazz.getClassLoader(); 471 String classFile = clazz.getName().replaceAll("\\.", "/") + ".class"; 472 try { 473 for (Enumeration<URL> itr = loader.getResources(classFile); itr.hasMoreElements();) { 474 URL url = itr.nextElement(); 475 if ("jar".equals(url.getProtocol())) { 476 String toReturn = url.getPath(); 477 if (toReturn.startsWith("file:")) { 478 toReturn = toReturn.substring("file:".length()); 479 // URLDecoder is a misnamed class, since it actually 480 // decodes 481 // x-www-form-urlencoded MIME type rather than actual 482 // URL encoding (which the file path has). Therefore it 483 // would 484 // decode +s to ' 's which is incorrect (spaces are 485 // actually 486 // either unencoded or encoded as "%20"). Replace +s 487 // first, so 488 // that they are kept sacred during the decoding 489 // process. 490 toReturn = toReturn.replaceAll("\\+", "%2B"); 491 toReturn = URLDecoder.decode(toReturn, "UTF-8"); 492 toReturn = toReturn.replaceAll("!.*$", ""); 493 return toReturn; 494 } 495 } 496 } 497 } 498 catch (IOException ioe) { 499 throw new RuntimeException(ioe); 500 } 501 return null; 502 } 503 504 /** 505 * Purge libs. 506 * 507 * @param fs the fs 508 * @param prefix the prefix 509 * @param current the current time 510 * @throws IOException Signals that an I/O exception has occurred. 511 */ 512 private void purgeLibs(FileSystem fs, final String prefix, final Date current) throws IOException { 513 Path executorLibBasePath = services.get(WorkflowAppService.class).getSystemLibPath(); 514 PathFilter directoryFilter = new PathFilter() { 515 @Override 516 public boolean accept(Path path) { 517 if (path.getName().startsWith(prefix)) { 518 String name = path.getName(); 519 String time = name.substring(prefix.length()); 520 Date d = null; 521 try { 522 d = dateFormat.parse(time); 523 } 524 catch (ParseException e) { 525 return false; 526 } 527 return (current.getTime() - d.getTime()) > retentionTime; 528 } 529 else { 530 return false; 531 } 532 } 533 }; 534 FileStatus[] dirList = fs.listStatus(executorLibBasePath, directoryFilter); 535 Arrays.sort(dirList, new Comparator<FileStatus>() { 536 // sort in desc order 537 @Override 538 public int compare(FileStatus o1, FileStatus o2) { 539 return o2.getPath().getName().compareTo(o1.getPath().getName()); 540 } 541 }); 542 543 // Logic is to keep all share-lib between current timestamp and 7days old + 1 latest sharelib older than 7 days. 544 // refer OOZIE-1761 545 for (int i = 1; i < dirList.length; i++) { 546 Path dirPath = dirList[i].getPath(); 547 fs.delete(dirPath, true); 548 LOG.info("Deleted old launcher jar lib directory {0}", dirPath.getName()); 549 } 550 } 551 552 @Override 553 public void destroy() { 554 shareLibMap.clear(); 555 launcherLibMap.clear(); 556 } 557 558 @Override 559 public Class<? extends Service> getInterface() { 560 return ShareLibService.class; 561 } 562 563 /** 564 * Update share lib cache. 565 * 566 * @return the map 567 * @throws IOException Signals that an I/O exception has occurred. 568 */ 569 public Map<String, String> updateShareLib() throws IOException { 570 Map<String, String> status = new HashMap<String, String>(); 571 572 if (fs == null) { 573 Path launcherlibPath = getLauncherlibPath(); 574 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 575 URI uri = launcherlibPath.toUri(); 576 fs = FileSystem.get(has.createConfiguration(uri.getAuthority())); 577 } 578 579 Map<String, List<Path>> tempShareLibMap = new HashMap<String, List<Path>>(); 580 Map<String, Map<Path, Path>> tmpSymlinkMapping = new HashMap<String, Map<Path, Path>>(); 581 Map<String, Map<Path, Configuration>> tmpShareLibConfigMap = new HashMap<String, Map<Path, Configuration>>(); 582 583 String trimmedSharelibMappingFile = sharelibMappingFile.trim(); 584 if (!StringUtils.isEmpty(trimmedSharelibMappingFile)) { 585 FileSystem fileSystem = getHostFileSystem(trimmedSharelibMappingFile); 586 587 String sharelibMetaFileNewTimeStamp = JsonUtils.formatDateRfc822( 588 new Date(fileSystem.getFileStatus(new Path(sharelibMappingFile)).getModificationTime()), "GMT"); 589 590 loadShareLibMetaFile(tempShareLibMap, tmpSymlinkMapping, tmpShareLibConfigMap, sharelibMappingFile, null); 591 status.put("sharelibMetaFile", sharelibMappingFile); 592 status.put("sharelibMetaFileNewTimeStamp", sharelibMetaFileNewTimeStamp); 593 status.put("sharelibMetaFileOldTimeStamp", sharelibMetaFileOldTimeStamp); 594 sharelibMetaFileOldTimeStamp = sharelibMetaFileNewTimeStamp; 595 } 596 else { 597 Path shareLibpath = getLatestLibPath(services.get(WorkflowAppService.class).getSystemLibPath(), 598 SHARE_LIB_PREFIX); 599 loadShareLibfromDFS(tempShareLibMap, shareLibpath, tmpShareLibConfigMap); 600 601 if (shareLibpath != null) { 602 status.put("sharelibDirNew", shareLibpath.toString()); 603 status.put("sharelibDirOld", sharelibDirOld); 604 sharelibDirOld = shareLibpath.toString(); 605 } 606 607 } 608 shareLibMap = tempShareLibMap; 609 symlinkMapping = tmpSymlinkMapping; 610 shareLibConfigMap = tmpShareLibConfigMap; 611 return status; 612 } 613 614 /** 615 * Update share lib cache. Parse the share lib directory and each sub directory is a action key 616 * 617 * @param shareLibMap the share lib jar map 618 * @param shareLibpath the share libpath 619 * @throws IOException Signals that an I/O exception has occurred. 620 */ 621 private void loadShareLibfromDFS(Map<String, List<Path>> shareLibMap, Path shareLibpath, 622 Map<String, Map<Path, Configuration>> shareLibConfigMap) throws IOException { 623 624 if (shareLibpath == null) { 625 LOG.info("No share lib directory found"); 626 return; 627 628 } 629 630 FileStatus[] dirList = fs.listStatus(shareLibpath); 631 632 if (dirList == null) { 633 return; 634 } 635 636 for (FileStatus dir : dirList) { 637 if (!dir.isDirectory()) { 638 continue; 639 } 640 List<Path> listOfPaths = new ArrayList<Path>(); 641 getPathRecursively(fs, dir.getPath(), listOfPaths, dir.getPath().getName(), shareLibConfigMap); 642 shareLibMap.put(dir.getPath().getName(), listOfPaths); 643 LOG.info("Share lib for " + dir.getPath().getName() + ":" + listOfPaths); 644 645 } 646 647 } 648 649 /** 650 * Load share lib text file. Sharelib mapping files contains list of key=value. where key is the action key and 651 * value is the DFS location of sharelib files. 652 * 653 * @param shareLibMap the share lib jar map 654 * @param symlinkMapping the symlink mapping 655 * @param sharelibFileMapping the sharelib file mapping 656 * @param shareLibKey the share lib key 657 * @throws IOException Signals that an I/O exception has occurred. 658 * @parm shareLibKey the sharelib key 659 */ 660 private void loadShareLibMetaFile(Map<String, List<Path>> shareLibMap, Map<String, Map<Path, Path>> symlinkMapping, 661 Map<String, Map<Path, Configuration>> shareLibConfigMap, String sharelibFileMapping, String shareLibKey) 662 throws IOException { 663 664 Path shareFileMappingPath = new Path(sharelibFileMapping); 665 FileSystem filesystem = getHostFileSystem(shareFileMappingPath); 666 667 Properties prop = new Properties(); 668 prop.load(filesystem.open(new Path(sharelibFileMapping))); 669 670 for (Object keyObject : prop.keySet()) { 671 String key = (String) keyObject; 672 String mapKey = key.substring(SHARE_LIB_CONF_PREFIX.length() + 1); 673 if (key.toLowerCase().startsWith(SHARE_LIB_CONF_PREFIX) 674 && (shareLibKey == null || shareLibKey.equals(mapKey))) { 675 loadSharelib(shareLibMap, symlinkMapping, shareLibConfigMap, mapKey, 676 ((String) prop.get(key)).split(",")); 677 } 678 } 679 } 680 681 private void loadSharelib(Map<String, List<Path>> tmpShareLibMap, Map<String, Map<Path, Path>> tmpSymlinkMapping, 682 Map<String, Map<Path, Configuration>> shareLibConfigMap, String shareLibKey, String pathList[]) 683 throws IOException { 684 List<Path> listOfPaths = new ArrayList<Path>(); 685 Map<Path, Path> symlinkMappingforAction = new HashMap<Path, Path>(); 686 687 for (String pathStr : pathList) { 688 Path path = new Path(pathStr); 689 final FileSystem fileSystem = getHostFileSystem(pathStr); 690 691 getPathRecursively(fileSystem, path, listOfPaths, shareLibKey, shareLibConfigMap); 692 if (FSUtils.isSymlink(fileSystem, path)) { 693 symlinkMappingforAction.put(path, FSUtils.getSymLinkTarget(fileSystem, path)); 694 } 695 } 696 697 LOG.info("symlink for " + shareLibKey + ":" + symlinkMappingforAction); 698 tmpSymlinkMapping.put(shareLibKey, symlinkMappingforAction); 699 700 tmpShareLibMap.put(shareLibKey, listOfPaths); 701 LOG.info("Share lib for " + shareLibKey + ":" + listOfPaths); 702 } 703 704 /** 705 * Gets the launcherlib path. 706 * 707 * @return the launcherlib path 708 */ 709 private Path getLauncherlibPath() { 710 String formattedDate = dateFormat.format(Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime()); 711 Path tmpLauncherLibPath = new Path(services.get(WorkflowAppService.class).getSystemLibPath(), LAUNCHER_LIB_PREFIX 712 + formattedDate); 713 return tmpLauncherLibPath; 714 } 715 716 /** 717 * Gets the Latest lib path. 718 * 719 * @param rootDir the root dir 720 * @param prefix the prefix 721 * @return latest lib path 722 * @throws IOException Signals that an I/O exception has occurred. 723 */ 724 public Path getLatestLibPath(Path rootDir, final String prefix) throws IOException { 725 Date max = new Date(0L); 726 Path path = null; 727 PathFilter directoryFilter = new PathFilter() { 728 @Override 729 public boolean accept(Path path) { 730 return path.getName().startsWith(prefix); 731 } 732 }; 733 734 FileStatus[] files = fs.listStatus(rootDir, directoryFilter); 735 for (FileStatus file : files) { 736 String name = file.getPath().getName().toString(); 737 String time = name.substring(prefix.length()); 738 Date d = null; 739 try { 740 d = dateFormat.parse(time); 741 } 742 catch (ParseException e) { 743 continue; 744 } 745 if (d.compareTo(max) > 0) { 746 path = file.getPath(); 747 max = d; 748 } 749 } 750 // If there are no timestamped directories, fall back to root directory 751 if (path == null) { 752 path = rootDir; 753 } 754 return path; 755 } 756 757 /** 758 * Instruments the log service. 759 * <p> 760 * It sets instrumentation variables indicating the location of the sharelib and launcherlib 761 * 762 * @param instr instrumentation to use. 763 */ 764 @Override 765 public void instrument(Instrumentation instr) { 766 instr.addVariable("libs", "sharelib.source", new Instrumentation.Variable<String>() { 767 @Override 768 public String getValue() { 769 if (!StringUtils.isEmpty(sharelibMappingFile.trim())) { 770 return SHARELIB_MAPPING_FILE; 771 } 772 return WorkflowAppService.SYSTEM_LIB_PATH; 773 } 774 }); 775 instr.addVariable("libs", "sharelib.mapping.file", new Instrumentation.Variable<String>() { 776 @Override 777 public String getValue() { 778 if (!StringUtils.isEmpty(sharelibMappingFile.trim())) { 779 return sharelibMappingFile; 780 } 781 return "(none)"; 782 } 783 }); 784 instr.addVariable("libs", "sharelib.system.libpath", new Instrumentation.Variable<String>() { 785 @Override 786 public String getValue() { 787 String sharelibPath = "(unavailable)"; 788 try { 789 Path libPath = getLatestLibPath(services.get(WorkflowAppService.class).getSystemLibPath(), 790 SHARE_LIB_PREFIX); 791 if (libPath != null) { 792 sharelibPath = libPath.toUri().toString(); 793 } 794 } 795 catch (IOException ioe) { 796 // ignore exception because we're just doing instrumentation 797 } 798 return sharelibPath; 799 } 800 }); 801 instr.addVariable("libs", "sharelib.mapping.file.timestamp", new Instrumentation.Variable<String>() { 802 @Override 803 public String getValue() { 804 if (!StringUtils.isEmpty(sharelibMetaFileOldTimeStamp)) { 805 return sharelibMetaFileOldTimeStamp; 806 } 807 return "(none)"; 808 } 809 }); 810 instr.addVariable("libs", "sharelib.keys", new Instrumentation.Variable<String>() { 811 @Override 812 public String getValue() { 813 Map<String, List<Path>> shareLib = getShareLib(); 814 if (shareLib != null && !shareLib.isEmpty()) { 815 Set<String> keySet = shareLib.keySet(); 816 return keySet.toString(); 817 } 818 return "(unavailable)"; 819 } 820 }); 821 instr.addVariable("libs", "launcherlib.system.libpath", new Instrumentation.Variable<String>() { 822 @Override 823 public String getValue() { 824 return getLauncherlibPath().toUri().toString(); 825 } 826 }); 827 instr.addVariable("libs", "sharelib.symlink.mapping", new Instrumentation.Variable<String>() { 828 @Override 829 public String getValue() { 830 Map<String, Map<Path, Path>> shareLibSymlinkMapping = getSymlinkMapping(); 831 if (shareLibSymlinkMapping != null && !shareLibSymlinkMapping.isEmpty() 832 && shareLibSymlinkMapping.values() != null && !shareLibSymlinkMapping.values().isEmpty()) { 833 StringBuffer bf = new StringBuffer(); 834 for (Entry<String, Map<Path, Path>> entry : shareLibSymlinkMapping.entrySet()) { 835 if (entry.getKey() != null && !entry.getValue().isEmpty()) { 836 for (Path path : entry.getValue().keySet()) { 837 bf.append(path).append("(").append(entry.getKey()).append(")").append("=>") 838 .append(shareLibSymlinkMapping.get(entry.getKey()) != null ? shareLibSymlinkMapping 839 .get(entry.getKey()).get(path) : "").append(","); 840 } 841 } 842 } 843 return bf.toString(); 844 } 845 return "(none)"; 846 } 847 }); 848 849 instr.addVariable("libs", "sharelib.cached.config.file", new Instrumentation.Variable<String>() { 850 @Override 851 public String getValue() { 852 Map<String, Map<Path, Configuration>> shareLibConfigMap = getShareLibConfigMap(); 853 if (shareLibConfigMap != null && !shareLibConfigMap.isEmpty()) { 854 StringBuffer bf = new StringBuffer(); 855 856 for (String path : shareLibConfigMap.keySet()) { 857 bf.append(path).append(";"); 858 } 859 return bf.toString(); 860 } 861 return "(none)"; 862 } 863 }); 864 865 } 866 867 /** 868 * Returns file system for shared libraries. 869 * <p> 870 * If WorkflowAppService#getSystemLibPath doesn't have authority then a default one assumed 871 * 872 * @return file system for shared libraries 873 */ 874 public FileSystem getFileSystem() { 875 return fs; 876 } 877 878 /** 879 * Cache XML conf file 880 * 881 * @param propertyFilePath the path of the property file 882 * @param shareLibKey the share lib key 883 * @throws IOException Signals that an I/O exception has occurred. 884 * @throws JDOMException 885 */ 886 private void cachePropertyFile(Path qualifiedHdfsPath, Path propertyFilePath, String shareLibKey, 887 Map<String, Map<Path, Configuration>> shareLibConfigMap) throws IOException, JDOMException { 888 Map<Path, Configuration> confMap = shareLibConfigMap.get(shareLibKey); 889 if (confMap == null) { 890 confMap = new HashMap<Path, Configuration>(); 891 shareLibConfigMap.put(shareLibKey, confMap); 892 } 893 FileSystem fileSystem = getHostFileSystem(propertyFilePath); 894 Configuration xmlConf = new XConfiguration(fileSystem.open(propertyFilePath)); 895 confMap.put(qualifiedHdfsPath, xmlConf); 896 } 897 898 private void cacheActionKeySharelibConfList() { 899 ActionService actionService = Services.get().get(ActionService.class); 900 Set<String> actionTypes = actionService.getActionTypes(); 901 for (String key : actionTypes) { 902 ActionExecutor executor = actionService.getExecutor(key); 903 if (executor instanceof JavaActionExecutor) { 904 JavaActionExecutor jexecutor = (JavaActionExecutor) executor; 905 actionConfSet.addAll( 906 new HashSet<String>(Arrays.asList(jexecutor.getShareLibFilesForActionConf() == null ? new String[0] 907 : jexecutor.getShareLibFilesForActionConf()))); 908 } 909 } 910 } 911 912 public Configuration getShareLibConf(String inputKey, Path path) { 913 if (shareLibConfigMap.containsKey(inputKey)) { 914 return shareLibConfigMap.get(inputKey).get(path); 915 } 916 917 return null; 918 } 919 920 @VisibleForTesting 921 public Map<String, Map<Path, Configuration>> getShareLibConfigMap() { 922 return shareLibConfigMap; 923 } 924 925 private boolean isFilePartOfConfList(Path path) throws URISyntaxException { 926 String fragmentName = new URI(path.toString()).getFragment(); 927 String fileName = fragmentName == null ? path.getName() : fragmentName; 928 return actionConfSet.contains(fileName); 929 } 930}