You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@twill.apache.org by ch...@apache.org on 2017/04/03 20:12:49 UTC

twill git commit: (TWILL-179) Added support for custom ClassLoader for containers

Repository: twill
Updated Branches:
  refs/heads/master 2a316a60b -> c8e2a615a


(TWILL-179) Added support for custom ClassLoader for containers

- Added method TwillPreparer.setClassLoader
- Use system property "twill.custom.class.loader" to pass the class name of the custom ClassLoader

This closes #51 on Github.

Signed-off-by: Terence Yim <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/twill/repo
Commit: http://git-wip-us.apache.org/repos/asf/twill/commit/c8e2a615
Tree: http://git-wip-us.apache.org/repos/asf/twill/tree/c8e2a615
Diff: http://git-wip-us.apache.org/repos/asf/twill/diff/c8e2a615

Branch: refs/heads/master
Commit: c8e2a615a2450c85e9344b50ee9ded562a54d018
Parents: 2a316a6
Author: Terence Yim <ch...@apache.org>
Authored: Mon Apr 3 12:34:14 2017 -0700
Committer: Terence Yim <ch...@apache.org>
Committed: Mon Apr 3 13:12:38 2017 -0700

----------------------------------------------------------------------
 .../org/apache/twill/api/TwillPreparer.java     | 13 +++
 .../org/apache/twill/internal/Constants.java    |  5 ++
 .../apache/twill/launcher/TwillLauncher.java    | 44 +++++++---
 .../apache/twill/yarn/YarnTwillPreparer.java    | 32 +++++--
 .../apache/twill/yarn/CustomClassLoader.java    | 87 ++++++++++++++++++++
 .../twill/yarn/CustomClassLoaderRunnable.java   | 56 +++++++++++++
 .../twill/yarn/CustomClassLoaderTestRun.java    | 42 ++++++++++
 .../org/apache/twill/yarn/YarnTestSuite.java    |  1 +
 8 files changed, 264 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-api/src/main/java/org/apache/twill/api/TwillPreparer.java
----------------------------------------------------------------------
diff --git a/twill-api/src/main/java/org/apache/twill/api/TwillPreparer.java b/twill-api/src/main/java/org/apache/twill/api/TwillPreparer.java
index 43b751b..1f50972 100644
--- a/twill-api/src/main/java/org/apache/twill/api/TwillPreparer.java
+++ b/twill-api/src/main/java/org/apache/twill/api/TwillPreparer.java
@@ -21,6 +21,7 @@ import org.apache.twill.api.logging.LogEntry;
 import org.apache.twill.api.logging.LogHandler;
 
 import java.net.URI;
+import java.net.URL;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -276,6 +277,18 @@ public interface TwillPreparer {
   TwillPreparer setLogLevels(String runnableName, Map<String, LogEntry.Level> logLevelsForRunnable);
 
   /**
+   * Sets the class name of the {@link ClassLoader} to be used for loading twill and application classes for
+   * all containers. The {@link ClassLoader} class should have a public constructor that takes two parameters in the
+   * form of {@code (URL[] urls, ClassLoader parentClassLoader)}.
+   * The first parameter is an array of {@link URL} that contains the list of {@link URL} for loading classes and
+   * resources; the second parameter is the parent {@link ClassLoader}.
+   *
+   * @param classLoaderClassName name of the {@link ClassLoader} class.
+   * @return This {@link TwillPreparer}.
+   */
+  TwillPreparer setClassLoader(String classLoaderClassName);
+
+  /**
    * Starts the application. It's the same as calling {@link #start(long, TimeUnit)} with timeout of 60 seconds.
    *
    * @return A {@link TwillController} for controlling the running application.

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-common/src/main/java/org/apache/twill/internal/Constants.java
----------------------------------------------------------------------
diff --git a/twill-common/src/main/java/org/apache/twill/internal/Constants.java b/twill-common/src/main/java/org/apache/twill/internal/Constants.java
index 6e799d5..4135c9a 100644
--- a/twill-common/src/main/java/org/apache/twill/internal/Constants.java
+++ b/twill-common/src/main/java/org/apache/twill/internal/Constants.java
@@ -53,6 +53,11 @@ public final class Constants {
   public static final String TWILL_APP_NAME = "TWILL_APP_NAME";
 
   /**
+   * Constant for the system property name that carries the class name for the container ClassLoader as defined by user.
+   */
+  public static final String TWILL_CONTAINER_CLASSLOADER = "twill.container.class.loader";
+
+  /**
    * Constants for names of internal files that are shared between client, AM and containers.
    */
   public static final class Files {

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-core/src/main/java/org/apache/twill/launcher/TwillLauncher.java
----------------------------------------------------------------------
diff --git a/twill-core/src/main/java/org/apache/twill/launcher/TwillLauncher.java b/twill-core/src/main/java/org/apache/twill/launcher/TwillLauncher.java
index a5052b3..056639f 100644
--- a/twill-core/src/main/java/org/apache/twill/launcher/TwillLauncher.java
+++ b/twill-core/src/main/java/org/apache/twill/launcher/TwillLauncher.java
@@ -22,13 +22,11 @@ import org.apache.twill.internal.Constants;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.util.ArrayList;
@@ -61,11 +59,12 @@ public final class TwillLauncher {
     boolean userClassPath = Boolean.parseBoolean(args[1]);
 
     // Create ClassLoader
-    URLClassLoader classLoader = createClassLoader(userClassPath);
-    Thread.currentThread().setContextClassLoader(classLoader);
+    URL[] classpath = createClasspath(userClassPath);
+    ClassLoader classLoader = createContainerClassLoader(classpath);
+    System.out.println("Launch class (" + mainClassName + ") using classloader " + classLoader.getClass().getName()
+                         + " with classpath: " + Arrays.toString(classpath));
 
-    System.out.println("Launch class (" + mainClassName + ") with classpath: " +
-                         Arrays.toString(classLoader.getURLs()));
+    Thread.currentThread().setContextClassLoader(classLoader);
 
     Class<?> mainClass = classLoader.loadClass(mainClassName);
     Method mainMethod = mainClass.getMethod("main", String[].class);
@@ -77,7 +76,7 @@ public final class TwillLauncher {
     System.out.println("Launcher completed");
   }
 
-  private static URLClassLoader createClassLoader(boolean useClassPath) throws Exception {
+  private static URL[] createClasspath(boolean useClassPath) throws IOException {
     List<URL> urls = new ArrayList<>();
 
     File appJarDir = new File(Constants.Files.APPLICATION_JAR);
@@ -116,7 +115,33 @@ public final class TwillLauncher {
     }
 
     addClassPathsToList(urls, new File(Constants.Files.RUNTIME_CONFIG_JAR, Constants.Files.APPLICATION_CLASSPATH));
-    return new URLClassLoader(urls.toArray(new URL[urls.size()]));
+    return urls.toArray(new URL[urls.size()]);
+  }
+
+  /**
+   * Creates a {@link ClassLoader} to be used by this container that load classes from the given classpath.
+   */
+  private static ClassLoader createContainerClassLoader(URL[] classpath) {
+    String containerClassLoaderName = System.getProperty(Constants.TWILL_CONTAINER_CLASSLOADER);
+    URLClassLoader classLoader = new URLClassLoader(classpath);
+    if (containerClassLoaderName == null) {
+      return classLoader;
+    }
+
+    try {
+      @SuppressWarnings("unchecked")
+      Class<? extends ClassLoader> cls = (Class<? extends ClassLoader>) classLoader.loadClass(containerClassLoaderName);
+
+      // Instantiate with constructor (URL[] classpath, ClassLoader parentClassLoader)
+      return cls.getConstructor(URL[].class, ClassLoader.class).newInstance(classpath, classLoader.getParent());
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException("Failed to load container class loader class " + containerClassLoaderName, e);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException("Container class loader must have a public constructor with " +
+                                   "parameters (URL[] classpath, ClassLoader parent)", e);
+    } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
+      throw new RuntimeException("Failed to create container class loader of class " + containerClassLoaderName, e);
+    }
   }
 
   private static void addClassPathsToList(List<URL> urls, File classpathFile) throws IOException {
@@ -172,7 +197,6 @@ public final class TwillLauncher {
    * Populates a list of {@link File} under the given directory that has ".jar" as extension.
    */
   private static List<File> listJarFiles(File dir, List<File> result) {
-    System.out.println("listing jars for " + dir.getAbsolutePath());
     File[] files = dir.listFiles();
     if (files == null || files.length == 0) {
       return result;

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-yarn/src/main/java/org/apache/twill/yarn/YarnTwillPreparer.java
----------------------------------------------------------------------
diff --git a/twill-yarn/src/main/java/org/apache/twill/yarn/YarnTwillPreparer.java b/twill-yarn/src/main/java/org/apache/twill/yarn/YarnTwillPreparer.java
index c8abf4f..52e18eb 100644
--- a/twill-yarn/src/main/java/org/apache/twill/yarn/YarnTwillPreparer.java
+++ b/twill-yarn/src/main/java/org/apache/twill/yarn/YarnTwillPreparer.java
@@ -146,11 +146,12 @@ final class YarnTwillPreparer implements TwillPreparer {
   private final Credentials credentials;
   private final Map<String, Map<String, String>> logLevels = Maps.newHashMap();
   private final LocationCache locationCache;
+  private final Map<String, Integer> maxRetries = Maps.newHashMap();
   private String schedulerQueue;
   private String extraOptions;
   private JvmOptions.DebugOptions debugOptions = JvmOptions.DebugOptions.NO_DEBUG;
   private ClassAcceptor classAcceptor;
-  private final Map<String, Integer> maxRetries = Maps.newHashMap();
+  private String classLoaderClassName;
 
   YarnTwillPreparer(Configuration config, TwillSpecification twillSpec, RunId runId,
                     String zkConnectString, Location appLocation, String extraOptions,
@@ -350,6 +351,12 @@ final class YarnTwillPreparer implements TwillPreparer {
   }
 
   @Override
+  public TwillPreparer setClassLoader(String classLoaderClassName) {
+    this.classLoaderClassName = classLoaderClassName;
+    return this;
+  }
+
+  @Override
   public TwillController start() {
     return start(Constants.APPLICATION_MAX_START_SECONDS, TimeUnit.SECONDS);
   }
@@ -365,6 +372,8 @@ final class YarnTwillPreparer implements TwillPreparer {
           @Override
           public ProcessController<YarnApplicationReport> call() throws Exception {
 
+            String extraOptions = getExtraOptions();
+
             // Local files needed by AM
             Map<String, LocalFile> localFiles = Maps.newHashMap();
 
@@ -379,7 +388,7 @@ final class YarnTwillPreparer implements TwillPreparer {
               saveSpecification(twillSpec, runtimeConfigDir.resolve(Constants.Files.TWILL_SPEC));
               saveLogback(runtimeConfigDir.resolve(Constants.Files.LOGBACK_TEMPLATE));
               saveClassPaths(runtimeConfigDir);
-              saveJvmOptions(runtimeConfigDir.resolve(Constants.Files.JVM_OPTIONS));
+              saveJvmOptions(extraOptions, debugOptions, runtimeConfigDir.resolve(Constants.Files.JVM_OPTIONS));
               saveArguments(new Arguments(arguments, runnableArgs),
                             runtimeConfigDir.resolve(Constants.Files.ARGUMENTS));
               saveEnvironments(runtimeConfigDir.resolve(Constants.Files.ENVIRONMENTS));
@@ -409,7 +418,7 @@ final class YarnTwillPreparer implements TwillPreparer {
                 "-Dtwill.app=$" + Constants.TWILL_APP_NAME,
                 "-cp", Constants.Files.LAUNCHER_JAR + ":$HADOOP_CONF_DIR",
                 "-Xmx" + memory + "m",
-                extraOptions == null ? "" : extraOptions,
+                extraOptions,
                 TwillLauncher.class.getName(),
                 ApplicationMasterMain.class.getName(),
                 Boolean.FALSE.toString())
@@ -452,6 +461,17 @@ final class YarnTwillPreparer implements TwillPreparer {
     return new File(config.get(Configs.Keys.LOCAL_STAGING_DIRECTORY, Configs.Defaults.LOCAL_STAGING_DIRECTORY));
   }
 
+  /**
+   * Returns the extra options for the container JVM.
+   */
+  private String getExtraOptions() {
+    String extraOptions = this.extraOptions == null ? "" : this.extraOptions;
+    if (classLoaderClassName != null) {
+      extraOptions += " -D" + Constants.TWILL_CONTAINER_CLASSLOADER + "=" + classLoaderClassName;
+    }
+    return extraOptions;
+  }
+
   private void setEnv(String runnableName, Map<String, String> env, boolean overwrite) {
     Map<String, String> environment = environments.get(runnableName);
     if (environment == null) {
@@ -742,9 +762,9 @@ final class YarnTwillPreparer implements TwillPreparer {
                 Joiner.on(':').join(classPaths).getBytes(StandardCharsets.UTF_8));
   }
 
-  private void saveJvmOptions(final Path targetPath) throws IOException {
-    if ((extraOptions == null || extraOptions.isEmpty()) &&
-      JvmOptions.DebugOptions.NO_DEBUG.equals(debugOptions)) {
+  private void saveJvmOptions(String extraOptions,
+                              JvmOptions.DebugOptions debugOptions, final Path targetPath) throws IOException {
+    if (extraOptions.isEmpty() && JvmOptions.DebugOptions.NO_DEBUG.equals(debugOptions)) {
       // If no vm options, no need to localize the file.
       return;
     }

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoader.java
----------------------------------------------------------------------
diff --git a/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoader.java b/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoader.java
new file mode 100644
index 0000000..51c1dc0
--- /dev/null
+++ b/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoader.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.twill.yarn;
+
+import org.apache.twill.api.ServiceAnnouncer;
+import org.apache.twill.common.Cancellable;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.GeneratorAdapter;
+import org.objectweb.asm.commons.Method;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * ClassLoader that generates a new class for the {@link CustomClassLoaderTestRun}.
+ */
+public final class CustomClassLoader extends URLClassLoader {
+
+  public CustomClassLoader(URL[] urls, ClassLoader parent) {
+    super(urls, parent);
+  }
+
+  @Override
+  protected Class<?> findClass(String name) throws ClassNotFoundException {
+    if (!CustomClassLoaderRunnable.GENERATED_CLASS_NAME.equals(name)) {
+      return super.findClass(name);
+    }
+
+    // Generate a class that look like this:
+    //
+    // public class Generated {
+    //
+    //   public void announce(ServiceAnnouncer announcer, String serviceName, int port) {
+    //     announcer.announce(serviceName, port);
+    //   }
+    // }
+    Type generatedClassType = Type.getObjectType(CustomClassLoaderRunnable.GENERATED_CLASS_NAME.replace('.', '/'));
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+    cw.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL,
+             generatedClassType.getInternalName(), null, Type.getInternalName(Object.class), null);
+
+    // Generate the default constructor, which just call super();
+    Method constructor = new Method("<init>", Type.VOID_TYPE, new Type[0]);
+    GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, constructor, null, null, cw);
+    mg.loadThis();
+    mg.invokeConstructor(Type.getType(Object.class), constructor);
+    mg.returnValue();
+    mg.endMethod();
+
+    // Generate the announce method
+    Method announce = new Method("announce", Type.VOID_TYPE, new Type[] {
+      Type.getType(ServiceAnnouncer.class), Type.getType(String.class), Type.INT_TYPE
+    });
+    mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, announce, null, null, cw);
+    mg.loadArg(0);
+    mg.loadArg(1);
+    mg.loadArg(2);
+    mg.invokeInterface(Type.getType(ServiceAnnouncer.class),
+                       new Method("announce", Type.getType(Cancellable.class), new Type[] {
+                         Type.getType(String.class), Type.INT_TYPE
+                       }));
+    mg.pop();
+    mg.returnValue();
+    mg.endMethod();
+    cw.visitEnd();
+
+    byte[] byteCode = cw.toByteArray();
+    return defineClass(CustomClassLoaderRunnable.GENERATED_CLASS_NAME, byteCode, 0, byteCode.length);
+  }
+}

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderRunnable.java
----------------------------------------------------------------------
diff --git a/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderRunnable.java b/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderRunnable.java
new file mode 100644
index 0000000..66bcd42
--- /dev/null
+++ b/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderRunnable.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.twill.yarn;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+import org.apache.twill.api.AbstractTwillRunnable;
+import org.apache.twill.api.ServiceAnnouncer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Runnable for testing custom classloader
+ */
+public final class CustomClassLoaderRunnable extends AbstractTwillRunnable {
+
+  static final String SERVICE_NAME = "custom.service";
+  static final String GENERATED_CLASS_NAME = "org.apache.twill.test.Generated";
+
+  private static final Logger LOG = LoggerFactory.getLogger(CustomClassLoaderRunnable.class);
+
+  private final CountDownLatch stopLatch = new CountDownLatch(1);
+
+  @Override
+  public void run() {
+    try {
+      Class<?> cls = Class.forName(GENERATED_CLASS_NAME);
+      java.lang.reflect.Method announce = cls.getMethod("announce", ServiceAnnouncer.class, String.class, int.class);
+      announce.invoke(cls.newInstance(), getContext(), SERVICE_NAME, 54321);
+      Uninterruptibles.awaitUninterruptibly(stopLatch);
+    } catch (Exception e) {
+      LOG.error("Failed to call announce on " + GENERATED_CLASS_NAME, e);
+    }
+  }
+
+  @Override
+  public void stop() {
+    stopLatch.countDown();
+  }
+}

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderTestRun.java
----------------------------------------------------------------------
diff --git a/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderTestRun.java b/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderTestRun.java
new file mode 100644
index 0000000..0ac43a6
--- /dev/null
+++ b/twill-yarn/src/test/java/org/apache/twill/yarn/CustomClassLoaderTestRun.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.twill.yarn;
+
+import org.apache.twill.api.TwillController;
+import org.apache.twill.api.logging.PrinterLogHandler;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+
+/**
+ * Unit test for testing custom classloader for containers.
+ */
+public class CustomClassLoaderTestRun extends BaseYarnTest {
+
+  @Test
+  public void testCustomClassLoader() throws Exception {
+    TwillController controller = getTwillRunner().prepare(new CustomClassLoaderRunnable())
+      .setClassLoader(CustomClassLoader.class.getName())
+      .addLogHandler(new PrinterLogHandler(new PrintWriter(System.out, true)))
+      .start();
+
+    Assert.assertTrue(waitForSize(controller.discoverService(CustomClassLoaderRunnable.SERVICE_NAME), 1, 120));
+    controller.terminate().get();
+  }
+}

http://git-wip-us.apache.org/repos/asf/twill/blob/c8e2a615/twill-yarn/src/test/java/org/apache/twill/yarn/YarnTestSuite.java
----------------------------------------------------------------------
diff --git a/twill-yarn/src/test/java/org/apache/twill/yarn/YarnTestSuite.java b/twill-yarn/src/test/java/org/apache/twill/yarn/YarnTestSuite.java
index 56172da..0911a3d 100644
--- a/twill-yarn/src/test/java/org/apache/twill/yarn/YarnTestSuite.java
+++ b/twill-yarn/src/test/java/org/apache/twill/yarn/YarnTestSuite.java
@@ -26,6 +26,7 @@ import org.junit.runners.Suite;
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
   ContainerSizeTestRun.class,
+  CustomClassLoaderTestRun.class,
   DebugTestRun.class,
   DistributeShellTestRun.class,
   EchoServerTestRun.class,