-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-24319][SPARK SUBMIT] Fix spark-submit execution where no main class is required. #21450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
a69850b
9052dff
12a5145
b03e3de
d5a9ca3
8a8067e
5a737d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,7 +90,8 @@ class SparkSubmitCommandBuilder extends AbstractCommandBuilder { | |
|
|
||
| final List<String> userArgs; | ||
| private final List<String> parsedArgs; | ||
| private final boolean requiresAppResource; | ||
| // Special command means no appResource and no mainClass required | ||
| private final boolean isSpecialCommand; | ||
| private final boolean isExample; | ||
|
|
||
| /** | ||
|
|
@@ -105,7 +106,7 @@ class SparkSubmitCommandBuilder extends AbstractCommandBuilder { | |
| * spark-submit argument list to be modified after creation. | ||
| */ | ||
| SparkSubmitCommandBuilder() { | ||
| this.requiresAppResource = true; | ||
| this.isSpecialCommand = false; | ||
| this.isExample = false; | ||
| this.parsedArgs = new ArrayList<>(); | ||
| this.userArgs = new ArrayList<>(); | ||
|
|
@@ -138,25 +139,30 @@ class SparkSubmitCommandBuilder extends AbstractCommandBuilder { | |
|
|
||
| case RUN_EXAMPLE: | ||
| isExample = true; | ||
| appResource = SparkLauncher.NO_RESOURCE; | ||
| submitArgs = args.subList(1, args.size()); | ||
| } | ||
|
|
||
| this.isExample = isExample; | ||
| OptionParser parser = new OptionParser(true); | ||
| parser.parse(submitArgs); | ||
| this.requiresAppResource = parser.requiresAppResource; | ||
| if (!submitArgs.isEmpty()) { | ||
|
||
| OptionParser parser = new OptionParser(true); | ||
| parser.parse(submitArgs); | ||
| this.isSpecialCommand = parser.isSpecialCommand; | ||
| } else { | ||
| this.isSpecialCommand = false; | ||
| } | ||
| } else { | ||
| this.isExample = isExample; | ||
| this.requiresAppResource = false; | ||
| this.isSpecialCommand = true; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public List<String> buildCommand(Map<String, String> env) | ||
| throws IOException, IllegalArgumentException { | ||
| if (PYSPARK_SHELL.equals(appResource) && requiresAppResource) { | ||
| if (PYSPARK_SHELL.equals(appResource) && !isSpecialCommand) { | ||
| return buildPySparkShellCommand(env); | ||
| } else if (SPARKR_SHELL.equals(appResource) && requiresAppResource) { | ||
| } else if (SPARKR_SHELL.equals(appResource) && !isSpecialCommand) { | ||
| return buildSparkRCommand(env); | ||
| } else { | ||
| return buildSparkSubmitCommand(env); | ||
|
|
@@ -166,18 +172,18 @@ public List<String> buildCommand(Map<String, String> env) | |
| List<String> buildSparkSubmitArgs() { | ||
| List<String> args = new ArrayList<>(); | ||
| OptionParser parser = new OptionParser(false); | ||
| final boolean requiresAppResource; | ||
| final boolean isSpecialCommand; | ||
|
|
||
| // If the user args array is not empty, we need to parse it to detect exactly what | ||
| // the user is trying to run, so that checks below are correct. | ||
| if (!userArgs.isEmpty()) { | ||
| parser.parse(userArgs); | ||
| requiresAppResource = parser.requiresAppResource; | ||
| isSpecialCommand = parser.isSpecialCommand; | ||
| } else { | ||
| requiresAppResource = this.requiresAppResource; | ||
| isSpecialCommand = this.isSpecialCommand; | ||
| } | ||
|
|
||
| if (!allowsMixedArguments && requiresAppResource) { | ||
| if (!allowsMixedArguments && !isSpecialCommand) { | ||
| checkArgument(appResource != null, "Missing application resource."); | ||
| } | ||
|
|
||
|
|
@@ -229,7 +235,7 @@ List<String> buildSparkSubmitArgs() { | |
| args.add(join(",", pyFiles)); | ||
| } | ||
|
|
||
| if (isExample) { | ||
| if (isExample && !isSpecialCommand) { | ||
| checkArgument(mainClass != null, "Missing example class name."); | ||
| } | ||
|
|
||
|
|
@@ -421,7 +427,7 @@ private List<String> findExamplesJars() { | |
|
|
||
| private class OptionParser extends SparkSubmitOptionParser { | ||
|
|
||
| boolean requiresAppResource = true; | ||
| boolean isSpecialCommand = false; | ||
| private final boolean errorOnUnknownArgs; | ||
|
|
||
| OptionParser(boolean errorOnUnknownArgs) { | ||
|
|
@@ -470,17 +476,14 @@ protected boolean handle(String opt, String value) { | |
| break; | ||
| case KILL_SUBMISSION: | ||
| case STATUS: | ||
| requiresAppResource = false; | ||
| isSpecialCommand = true; | ||
| parsedArgs.add(opt); | ||
| parsedArgs.add(value); | ||
| break; | ||
| case HELP: | ||
| case USAGE_ERROR: | ||
| requiresAppResource = false; | ||
| parsedArgs.add(opt); | ||
| break; | ||
| case VERSION: | ||
| requiresAppResource = false; | ||
| isSpecialCommand = true; | ||
| parsedArgs.add(opt); | ||
| break; | ||
| default: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| package org.apache.spark.launcher; | ||
|
|
||
| import java.io.File; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
|
|
@@ -27,7 +28,10 @@ | |
|
|
||
| import org.junit.AfterClass; | ||
| import org.junit.BeforeClass; | ||
| import org.junit.Rule; | ||
| import org.junit.Test; | ||
| import org.junit.rules.ExpectedException; | ||
|
|
||
| import static org.junit.Assert.*; | ||
|
|
||
| public class SparkSubmitCommandBuilderSuite extends BaseSuite { | ||
|
|
@@ -74,8 +78,11 @@ public void testCliHelpAndNoArg() throws Exception { | |
|
|
||
| @Test | ||
| public void testCliKillAndStatus() throws Exception { | ||
| testCLIOpts(parser.STATUS); | ||
| testCLIOpts(parser.KILL_SUBMISSION); | ||
| List<String> params = Arrays.asList("driver-20160531171222-0000"); | ||
| testCLIOpts(null, parser.STATUS, params); | ||
| testCLIOpts(null, parser.KILL_SUBMISSION, params); | ||
| testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.STATUS, params); | ||
| testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.KILL_SUBMISSION, params); | ||
| } | ||
|
|
||
| @Test | ||
|
|
@@ -190,6 +197,36 @@ public void testSparkRShell() throws Exception { | |
| env.get("SPARKR_SUBMIT_ARGS")); | ||
| } | ||
|
|
||
| @Test(expected = IllegalArgumentException.class) | ||
| public void testExamplesRunnerNoArg() throws Exception { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When no args are given this is what I get as the output: It doesn't really make sense asking for a primary resource when running an example, how about just printing usage? This is what spark-submit does with no args
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, the output should make sense also. When the launcher library detects an invalid command it should be running Seems that's not exactly what's happening here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now it prints out the usage. |
||
| List<String> sparkSubmitArgs = Arrays.asList(SparkSubmitCommandBuilder.RUN_EXAMPLE); | ||
| Map<String, String> env = new HashMap<>(); | ||
| buildCommand(sparkSubmitArgs, env); | ||
| } | ||
|
|
||
| @Test | ||
| public void testExamplesRunnerNoMainClass() throws Exception { | ||
| testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.HELP, null); | ||
| testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.USAGE_ERROR, null); | ||
| testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.VERSION, null); | ||
| } | ||
|
|
||
| @Rule | ||
| public ExpectedException testExamplesRunnerWithMasterNoMainClassEx = ExpectedException.none(); | ||
|
|
||
| @Test | ||
| public void testExamplesRunnerWithMasterNoMainClass() throws Exception { | ||
| testExamplesRunnerWithMasterNoMainClassEx.expect(IllegalArgumentException.class); | ||
|
||
| testExamplesRunnerWithMasterNoMainClassEx.expectMessage("Missing example class name."); | ||
|
|
||
| List<String> sparkSubmitArgs = Arrays.asList( | ||
| SparkSubmitCommandBuilder.RUN_EXAMPLE, | ||
| parser.MASTER + "=foo" | ||
| ); | ||
| Map<String, String> env = new HashMap<>(); | ||
| buildCommand(sparkSubmitArgs, env); | ||
| } | ||
|
|
||
| @Test | ||
| public void testExamplesRunner() throws Exception { | ||
| List<String> sparkSubmitArgs = Arrays.asList( | ||
|
|
@@ -344,10 +381,17 @@ private List<String> buildCommand(List<String> args, Map<String, String> env) th | |
| return newCommandBuilder(args).buildCommand(env); | ||
| } | ||
|
|
||
| private void testCLIOpts(String opt) throws Exception { | ||
| List<String> helpArgs = Arrays.asList(opt, "driver-20160531171222-0000"); | ||
| private void testCLIOpts(String appResource, String opt, List<String> params) throws Exception { | ||
| List<String> args = new ArrayList<>(); | ||
| if (appResource != null) { | ||
| args.add(appResource); | ||
| } | ||
| args.add(opt); | ||
| if (params != null) { | ||
| args.addAll(params); | ||
| } | ||
| Map<String, String> env = new HashMap<>(); | ||
| List<String> cmd = buildCommand(helpArgs, env); | ||
| List<String> cmd = buildCommand(args, env); | ||
| assertTrue(opt + " should be contained in the final cmd.", | ||
| cmd.contains(opt)); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow the Spark style for multi-line parameter lists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Just for the sake of my understanding what does
manymean in the following? All?For Java code, Apache Spark follows Oracle’s Java code conventions. Many Scala guidelines below also apply to Java.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read that as "unless it cannot apply to Java at all, follow the Scala style".