[NEMO-304] Fail-fast for mis-configuration in user application (#167)
JIRA: [NEMO-304: Fail-fast for mis-configuration in user application](https://issues.apache.org/jira/projects/NEMO/issues/NEMO-304)
**Major changes:**
- This PR makes JobLauncher check the validity of user main method early, so that we can avoid initializing the whole system if there is a mis-configuration.
**Minor changes to note:**
-
**Tests for the changes:**
- Added `testNotExistingUserMain` in SparkScala.java to test whether the `InvalidUserMainException` is thrown if the argument of `addUserMain()` is a non-existing class.
**Other comments:**
-
Closes #167
Co-authored-by: Taegeon Um <taegeonum@gmail.com>
Co-authored-by: Won Wook SONG <wonook@apache.org>
diff --git a/client/src/main/java/org/apache/nemo/client/JobLauncher.java b/client/src/main/java/org/apache/nemo/client/JobLauncher.java
index 4559bad..9873f16 100644
--- a/client/src/main/java/org/apache/nemo/client/JobLauncher.java
+++ b/client/src/main/java/org/apache/nemo/client/JobLauncher.java
@@ -20,6 +20,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
+import org.apache.nemo.common.exception.InvalidUserMainException;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.nemo.common.Util;
import org.apache.nemo.common.ir.IRDAG;
@@ -121,10 +122,12 @@
* @throws ClassNotFoundException class not found exception.
* @throws IOException IO exception.
*/
- public static void setup(final String[] args) throws InjectionException, ClassNotFoundException, IOException {
+ public static void setup(final String[] args)
+ throws InjectionException, ClassNotFoundException, IOException, InvalidUserMainException {
// Get Job and Driver Confs
LOG.info("Project Root Path: {}", Util.fetchProjectRootPath());
builtJobConf = getJobConf(args);
+ validateJobConfig(builtJobConf);
// Registers actions for launching the DAG.
LOG.info("Launching RPC Server");
@@ -207,6 +210,29 @@
}
/**
+ * Validate the configuration of the application's main method.
+ * @param jobConf Configuration of the application.
+ * @throws InvalidUserMainException when the user main is invalid (e.g., non-existing class/method).
+ */
+ private static void validateJobConfig(final Configuration jobConf) throws InvalidUserMainException {
+ final Injector injector = TANG.newInjector(jobConf);
+ try {
+ final String className = injector.getNamedInstance(JobConf.UserMainClass.class);
+ final Class userCode = Class.forName(className);
+ final Method method = userCode.getMethod("main", String[].class);
+ if (!Modifier.isStatic(method.getModifiers())) {
+ throw new InvalidUserMainException("User Main Method not static");
+ }
+ if (!Modifier.isPublic(userCode.getModifiers())) {
+ throw new InvalidUserMainException("User Main Class not public");
+ }
+
+ } catch (final InjectionException | ClassNotFoundException | NoSuchMethodException e) {
+ throw new InvalidUserMainException(e);
+ }
+ }
+
+ /**
* Launch application using the application DAG.
* Notice that we launch the DAG one at a time, as the result of a DAG has to be immediately returned to the
* Java variable before the application can be resumed.
@@ -294,12 +320,6 @@
final String[] args = userArgsString.isEmpty() ? EMPTY_USER_ARGS : userArgsString.split(" ");
final Class userCode = Class.forName(className);
final Method method = userCode.getMethod("main", String[].class);
- if (!Modifier.isStatic(method.getModifiers())) {
- throw new RuntimeException("User Main Method not static");
- }
- if (!Modifier.isPublic(userCode.getModifiers())) {
- throw new RuntimeException("User Main Class not public");
- }
LOG.info("User program started");
method.invoke(null, (Object) args);
diff --git a/common/src/main/java/org/apache/nemo/common/exception/InvalidUserMainException.java b/common/src/main/java/org/apache/nemo/common/exception/InvalidUserMainException.java
new file mode 100644
index 0000000..13cbd3c
--- /dev/null
+++ b/common/src/main/java/org/apache/nemo/common/exception/InvalidUserMainException.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.nemo.common.exception;
+
+/**
+ * InvalidUserMainException.
+ * Thrown when an application's main class is invalid.
+ */
+public final class InvalidUserMainException extends Exception {
+
+ /**
+ * InvalidUserMainException.
+ * @param cause cause
+ */
+ public InvalidUserMainException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * InvalidUserMainException.
+ * @param message message
+ */
+ public InvalidUserMainException(final String message) {
+ super(message);
+ }
+}
diff --git a/examples/spark/src/test/java/org/apache/nemo/examples/spark/SparkScala.java b/examples/spark/src/test/java/org/apache/nemo/examples/spark/SparkScala.java
index f0731c3..db77804 100644
--- a/examples/spark/src/test/java/org/apache/nemo/examples/spark/SparkScala.java
+++ b/examples/spark/src/test/java/org/apache/nemo/examples/spark/SparkScala.java
@@ -19,6 +19,7 @@
package org.apache.nemo.examples.spark;
import org.apache.nemo.client.JobLauncher;
+import org.apache.nemo.common.exception.InvalidUserMainException;
import org.apache.nemo.common.test.ArgBuilder;
import org.apache.nemo.common.test.ExampleTestArgs;
import org.apache.nemo.common.test.ExampleTestUtil;
@@ -126,4 +127,15 @@
.addOptimizationPolicy(DefaultPolicy.class.getCanonicalName())
.build());
}
+
+ @Test(expected = InvalidUserMainException.class)
+ public void testNotExistingUserMain() throws Exception {
+ final String notExistingClassName = "XX";
+ JobLauncher.main(builder
+ .addJobId(SparkALS.class.getSimpleName() + "_test")
+ .addUserMain(notExistingClassName)
+ .addUserArgs("100")
+ .addOptimizationPolicy(DefaultPolicy.class.getCanonicalName())
+ .build());
+ }
}