diff --git hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/JarFinder.java hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/JarFinder.java index 33aa025..478a29b 100644 --- hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/JarFinder.java +++ hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/JarFinder.java @@ -14,7 +14,7 @@ package org.apache.hadoop.util; import com.google.common.base.Preconditions; - +import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; @@ -173,4 +173,28 @@ else if ("file".equals(url.getProtocol())) { } return null; } + + public static File makeClassLoaderTestJar(Class target, File rootDir, + String jarName, int buffSize, String... clsNames) throws IOException { + File jarFile = new File(rootDir, jarName); + JarOutputStream jstream = + new JarOutputStream(new FileOutputStream(jarFile)); + for (String clsName: clsNames) { + String name = clsName.replace('.', '/') + ".class"; + InputStream entryInputStream = target.getResourceAsStream( + "/" + name); + ZipEntry entry = new ZipEntry(name); + jstream.putNextEntry(entry); + BufferedInputStream bufInputStream = new BufferedInputStream( + entryInputStream, buffSize); + int count; + byte[] data = new byte[buffSize]; + while ((count = bufInputStream.read(data, 0, buffSize)) != -1) { + jstream.write(data, 0, count); + } + jstream.closeEntry(); + } + jstream.close(); + return jarFile; + } } diff --git hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestRunJar.java hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestRunJar.java index 6622389..7b61b32 100644 --- hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestRunJar.java +++ hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestRunJar.java @@ -23,11 +23,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.jar.JarOutputStream; import java.util.regex.Pattern; import java.util.zip.ZipEntry; @@ -156,7 +154,8 @@ public void testClientClassLoader() throws Throwable { when(runJar.getSystemClasses()).thenReturn(systemClasses); // create the test jar - File testJar = makeClassLoaderTestJar(mainCls, thirdCls); + File testJar = JarFinder.makeClassLoaderTestJar(this.getClass(), + TEST_ROOT_DIR, TEST_JAR_2_NAME, BUFF_SIZE, mainCls, thirdCls); // form the args String[] args = new String[3]; args[0] = testJar.getAbsolutePath(); @@ -166,28 +165,4 @@ public void testClientClassLoader() throws Throwable { runJar.run(args); // it should not throw an exception } - - private File makeClassLoaderTestJar(String... clsNames) throws IOException { - File jarFile = new File(TEST_ROOT_DIR, TEST_JAR_2_NAME); - JarOutputStream jstream = - new JarOutputStream(new FileOutputStream(jarFile)); - for (String clsName: clsNames) { - String name = clsName.replace('.', '/') + ".class"; - InputStream entryInputStream = this.getClass().getResourceAsStream( - "/" + name); - ZipEntry entry = new ZipEntry(name); - jstream.putNextEntry(entry); - BufferedInputStream bufInputStream = new BufferedInputStream( - entryInputStream, BUFF_SIZE); - int count; - byte[] data = new byte[BUFF_SIZE]; - while ((count = bufInputStream.read(data, 0, BUFF_SIZE)) != -1) { - jstream.write(data, 0, count); - } - jstream.closeEntry(); - } - jstream.close(); - - return jarFile; - } } \ No newline at end of file diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index 4099354..52355ab 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -1387,10 +1387,16 @@ public static boolean isAclEnabled(Configuration conf) { NM_PREFIX + "principal"; public static final String NM_AUX_SERVICES = - NM_PREFIX + "aux-services"; + NM_PREFIX + "aux-services"; public static final String NM_AUX_SERVICE_FMT = - NM_PREFIX + "aux-services.%s.class"; + NM_PREFIX + "aux-services.%s.class"; + + public static final String NM_AUX_SERVICES_CLASSPATH = + NM_AUX_SERVICES + ".%s.classpath"; + + public static final String NM_AUX_SERVICES_SYSTEM_CLASSES = + NM_AUX_SERVICES + ".%s.system-classes"; public static final String NM_USER_HOME_DIR = NM_PREFIX + "user-home-dir"; diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/AuxServices.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/AuxServices.java index cd5ed88..171b20d 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/AuxServices.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/AuxServices.java @@ -118,21 +118,40 @@ public void serviceInit(Configuration conf) throws Exception { YarnConfiguration.NM_AUX_SERVICES +" is invalid." + "The valid service name should only contain a-zA-Z0-9_ " + "and can not start with numbers"); - Class sClass = conf.getClass( - String.format(YarnConfiguration.NM_AUX_SERVICE_FMT, sName), null, - AuxiliaryService.class); - if (null == sClass) { - throw new RuntimeException("No class defined for " + sName); + String classKey = String.format( + YarnConfiguration.NM_AUX_SERVICE_FMT, sName); + String className = conf.get(classKey); + final String appClassPath = conf.get(String.format( + YarnConfiguration.NM_AUX_SERVICES_CLASSPATH, sName)); + AuxiliaryService s = null; + boolean useCustomerClassLoader = appClassPath != null + && !appClassPath.isEmpty() && className != null + && !className.isEmpty(); + if (useCustomerClassLoader) { + s = AuxiliaryServiceWithCustomClassLoader.getInstance( + conf, className, appClassPath); + LOG.info("The aux service:" + sName + + " are using the custom classloader"); + } else { + Class sClass = conf.getClass( + classKey, null, AuxiliaryService.class); + + if (sClass == null) { + throw new RuntimeException("No class defined for " + sName); + } + s = ReflectionUtils.newInstance(sClass, conf); + } + if (s == null) { + throw new RuntimeException("No object created for " + sName); } - AuxiliaryService s = ReflectionUtils.newInstance(sClass, conf); // TODO better use s.getName()? if(!sName.equals(s.getName())) { LOG.warn("The Auxilurary Service named '"+sName+"' in the " - +"configuration is for "+sClass+" which has " - +"a name of '"+s.getName()+"'. Because these are " - +"not the same tools trying to send ServiceData and read " - +"Service Meta Data may have issues unless the refer to " - +"the name in the config."); + +"configuration is for "+s.getClass()+" which has " + +"a name of '"+s.getName()+"'. Because these are " + +"not the same tools trying to send ServiceData and read " + +"Service Meta Data may have issues unless the refer to " + +"the name in the config."); } addService(sName, s); if (recoveryEnabled) { @@ -264,4 +283,4 @@ private void logWarningWhenAuxServiceThrowExceptions(AuxiliaryService service, : "The auxService name is " + service.getName()) + " and it got an error at event: " + eventType, th); } -} +} \ No newline at end of file diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/TestAuxServices.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/TestAuxServices.java index 9d0d0c0..26a1003 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/TestAuxServices.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/TestAuxServices.java @@ -27,13 +27,21 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; +import com.google.common.base.Charsets; +import com.google.common.collect.Sets; import java.io.File; import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; +import java.util.List; import java.util.Map; - +import java.util.Map.Entry; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -42,6 +50,10 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.service.Service; +import org.apache.hadoop.test.GenericTestUtils; +import org.apache.hadoop.util.ApplicationClassLoader; +import org.apache.hadoop.util.JarFinder; +import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.yarn.api.records.ApplicationAttemptId; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ContainerId; @@ -150,6 +162,111 @@ public ServiceB() { } } + // Override getMetaData() method to return current + // class path. This class would be used for + // testCustomizedAuxServiceClassPath. + static class ServiceC extends LightService { + public ServiceC() { + super("C", 'C', 66, ByteBuffer.wrap("C".getBytes())); + } + + @Override + public ByteBuffer getMetaData() { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + URL[] urls = ((URLClassLoader)loader).getURLs(); + List urlString = new ArrayList(); + for (URL url : urls) { + urlString.add(url.toString()); + } + String joinedString = StringUtils.join(",", urlString); + return ByteBuffer.wrap(joinedString.getBytes()); + } + } + + // To verify whether we could load class from customized class path. + // We would use ServiceC in this test. Also create a separate jar file + // including ServiceC class, and add this jar to customized directory. + // By setting some proper configurations, we should load ServiceC class + // from customized class path. + @Test (timeout = 15000) + public void testCustomizedAuxServiceClassPath() throws Exception { + // verify that we can load AuxService Class from default Class path + Configuration conf = new YarnConfiguration(); + conf.setStrings(YarnConfiguration.NM_AUX_SERVICES, + new String[] {"ServiceC"}); + conf.setClass(String.format(YarnConfiguration.NM_AUX_SERVICE_FMT, + "ServiceC"), ServiceC.class, Service.class); + @SuppressWarnings("resource") + AuxServices aux = new AuxServices(); + aux.init(conf); + aux.start(); + Map meta = aux.getMetaData(); + String auxName = ""; + Set defaultAuxClassPath = null; + Assert.assertTrue(meta.size() == 1); + for(Entry i : meta.entrySet()) { + auxName = i.getKey(); + String auxClassPath = Charsets.UTF_8.decode(i.getValue()).toString(); + defaultAuxClassPath = new HashSet(Arrays.asList(StringUtils + .getTrimmedStrings(auxClassPath))); + } + Assert.assertTrue(auxName.equals("ServiceC")); + aux.serviceStop(); + + // create a new jar file, and configure it as customized class path + // for this AuxService, and make sure that we could load the class + // from this configured customized class path + File rootDir = GenericTestUtils.getTestDir(getClass() + .getSimpleName()); + if (!rootDir.exists()) { + rootDir.mkdirs(); + } + File testJar = null; + try { + testJar = JarFinder.makeClassLoaderTestJar(this.getClass(), rootDir, + "test-runjar.jar", 2048, ServiceC.class.getName()); + conf = new YarnConfiguration(); + conf.setStrings(YarnConfiguration.NM_AUX_SERVICES, + new String[] {"ServiceC"}); + conf.set(String.format(YarnConfiguration.NM_AUX_SERVICE_FMT, "ServiceC"), + ServiceC.class.getName()); + conf.set(String.format( + YarnConfiguration.NM_AUX_SERVICES_CLASSPATH, "ServiceC"), + testJar.getAbsolutePath()); + // remove "-org.apache.hadoop." from system classes + String systemClasses = "-org.apache.hadoop." + "," + + ApplicationClassLoader.SYSTEM_CLASSES_DEFAULT; + conf.set(String.format( + YarnConfiguration.NM_AUX_SERVICES_SYSTEM_CLASSES, + "ServiceC"), systemClasses); + aux = new AuxServices(); + aux.init(conf); + aux.start(); + meta = aux.getMetaData(); + Assert.assertTrue(meta.size() == 1); + Set customizedAuxClassPath = null; + for(Entry i : meta.entrySet()) { + Assert.assertTrue(auxName.equals(i.getKey())); + String classPath = Charsets.UTF_8.decode(i.getValue()).toString(); + customizedAuxClassPath = new HashSet(Arrays.asList(StringUtils + .getTrimmedStrings(classPath))); + Assert.assertTrue(classPath.contains(testJar.getName())); + } + aux.stop(); + + // Verify that we do not have any overlap between customized class path + // and the default class path. + Set mutalClassPath = Sets.intersection(defaultAuxClassPath, + customizedAuxClassPath); + Assert.assertTrue(mutalClassPath.isEmpty()); + } finally { + if (testJar != null) { + testJar.delete(); + rootDir.delete(); + } + } + } + @Test public void testAuxEventDispatch() { Configuration conf = new Configuration();