Skip to content

Commit 18cb0c0

Browse files
gaborgsomogyiMarcelo Vanzin
authored andcommitted
[SPARK-24319][SPARK SUBMIT] Fix spark-submit execution where no main class is required.
## What changes were proposed in this pull request? With [PR 20925](#20925) now it's not possible to execute the following commands: * run-example * run-example --help * run-example --version * run-example --usage-error * run-example --status ... * run-example --kill ... In this PR the execution will be allowed for the mentioned commands. ## How was this patch tested? Existing unit tests extended + additional written. Author: Gabor Somogyi <gabor.g.somogyi@gmail.com> Closes #21450 from gaborgsomogyi/SPARK-24319.
1 parent b8f27ae commit 18cb0c0

3 files changed

Lines changed: 90 additions & 33 deletions

File tree

launcher/src/main/java/org/apache/spark/launcher/Main.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.spark.launcher;
1919

20+
import java.io.IOException;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.HashMap;
@@ -54,10 +55,12 @@ public static void main(String[] argsArray) throws Exception {
5455
String className = args.remove(0);
5556

5657
boolean printLaunchCommand = !isEmpty(System.getenv("SPARK_PRINT_LAUNCH_COMMAND"));
57-
AbstractCommandBuilder builder;
58+
Map<String, String> env = new HashMap<>();
59+
List<String> cmd;
5860
if (className.equals("org.apache.spark.deploy.SparkSubmit")) {
5961
try {
60-
builder = new SparkSubmitCommandBuilder(args);
62+
AbstractCommandBuilder builder = new SparkSubmitCommandBuilder(args);
63+
cmd = buildCommand(builder, env, printLaunchCommand);
6164
} catch (IllegalArgumentException e) {
6265
printLaunchCommand = false;
6366
System.err.println("Error: " + e.getMessage());
@@ -76,17 +79,12 @@ public static void main(String[] argsArray) throws Exception {
7679
help.add(parser.className);
7780
}
7881
help.add(parser.USAGE_ERROR);
79-
builder = new SparkSubmitCommandBuilder(help);
82+
AbstractCommandBuilder builder = new SparkSubmitCommandBuilder(help);
83+
cmd = buildCommand(builder, env, printLaunchCommand);
8084
}
8185
} else {
82-
builder = new SparkClassCommandBuilder(className, args);
83-
}
84-
85-
Map<String, String> env = new HashMap<>();
86-
List<String> cmd = builder.buildCommand(env);
87-
if (printLaunchCommand) {
88-
System.err.println("Spark Command: " + join(" ", cmd));
89-
System.err.println("========================================");
86+
AbstractCommandBuilder builder = new SparkClassCommandBuilder(className, args);
87+
cmd = buildCommand(builder, env, printLaunchCommand);
9088
}
9189

9290
if (isWindows()) {
@@ -101,6 +99,22 @@ public static void main(String[] argsArray) throws Exception {
10199
}
102100
}
103101

102+
/**
103+
* Prepare spark commands with the appropriate command builder.
104+
* If printLaunchCommand is set then the commands will be printed to the stderr.
105+
*/
106+
private static List<String> buildCommand(
107+
AbstractCommandBuilder builder,
108+
Map<String, String> env,
109+
boolean printLaunchCommand) throws IOException, IllegalArgumentException {
110+
List<String> cmd = builder.buildCommand(env);
111+
if (printLaunchCommand) {
112+
System.err.println("Spark Command: " + join(" ", cmd));
113+
System.err.println("========================================");
114+
}
115+
return cmd;
116+
}
117+
104118
/**
105119
* Prepare a command line for execution from a Windows batch script.
106120
*

launcher/src/main/java/org/apache/spark/launcher/SparkSubmitCommandBuilder.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ class SparkSubmitCommandBuilder extends AbstractCommandBuilder {
9090

9191
final List<String> userArgs;
9292
private final List<String> parsedArgs;
93-
private final boolean requiresAppResource;
93+
// Special command means no appResource and no mainClass required
94+
private final boolean isSpecialCommand;
9495
private final boolean isExample;
9596

9697
/**
@@ -105,7 +106,7 @@ class SparkSubmitCommandBuilder extends AbstractCommandBuilder {
105106
* spark-submit argument list to be modified after creation.
106107
*/
107108
SparkSubmitCommandBuilder() {
108-
this.requiresAppResource = true;
109+
this.isSpecialCommand = false;
109110
this.isExample = false;
110111
this.parsedArgs = new ArrayList<>();
111112
this.userArgs = new ArrayList<>();
@@ -138,25 +139,26 @@ class SparkSubmitCommandBuilder extends AbstractCommandBuilder {
138139

139140
case RUN_EXAMPLE:
140141
isExample = true;
142+
appResource = SparkLauncher.NO_RESOURCE;
141143
submitArgs = args.subList(1, args.size());
142144
}
143145

144146
this.isExample = isExample;
145147
OptionParser parser = new OptionParser(true);
146148
parser.parse(submitArgs);
147-
this.requiresAppResource = parser.requiresAppResource;
149+
this.isSpecialCommand = parser.isSpecialCommand;
148150
} else {
149151
this.isExample = isExample;
150-
this.requiresAppResource = false;
152+
this.isSpecialCommand = true;
151153
}
152154
}
153155

154156
@Override
155157
public List<String> buildCommand(Map<String, String> env)
156158
throws IOException, IllegalArgumentException {
157-
if (PYSPARK_SHELL.equals(appResource) && requiresAppResource) {
159+
if (PYSPARK_SHELL.equals(appResource) && !isSpecialCommand) {
158160
return buildPySparkShellCommand(env);
159-
} else if (SPARKR_SHELL.equals(appResource) && requiresAppResource) {
161+
} else if (SPARKR_SHELL.equals(appResource) && !isSpecialCommand) {
160162
return buildSparkRCommand(env);
161163
} else {
162164
return buildSparkSubmitCommand(env);
@@ -166,18 +168,18 @@ public List<String> buildCommand(Map<String, String> env)
166168
List<String> buildSparkSubmitArgs() {
167169
List<String> args = new ArrayList<>();
168170
OptionParser parser = new OptionParser(false);
169-
final boolean requiresAppResource;
171+
final boolean isSpecialCommand;
170172

171173
// If the user args array is not empty, we need to parse it to detect exactly what
172174
// the user is trying to run, so that checks below are correct.
173175
if (!userArgs.isEmpty()) {
174176
parser.parse(userArgs);
175-
requiresAppResource = parser.requiresAppResource;
177+
isSpecialCommand = parser.isSpecialCommand;
176178
} else {
177-
requiresAppResource = this.requiresAppResource;
179+
isSpecialCommand = this.isSpecialCommand;
178180
}
179181

180-
if (!allowsMixedArguments && requiresAppResource) {
182+
if (!allowsMixedArguments && !isSpecialCommand) {
181183
checkArgument(appResource != null, "Missing application resource.");
182184
}
183185

@@ -229,7 +231,7 @@ List<String> buildSparkSubmitArgs() {
229231
args.add(join(",", pyFiles));
230232
}
231233

232-
if (isExample) {
234+
if (isExample && !isSpecialCommand) {
233235
checkArgument(mainClass != null, "Missing example class name.");
234236
}
235237

@@ -421,7 +423,7 @@ private List<String> findExamplesJars() {
421423

422424
private class OptionParser extends SparkSubmitOptionParser {
423425

424-
boolean requiresAppResource = true;
426+
boolean isSpecialCommand = false;
425427
private final boolean errorOnUnknownArgs;
426428

427429
OptionParser(boolean errorOnUnknownArgs) {
@@ -470,17 +472,14 @@ protected boolean handle(String opt, String value) {
470472
break;
471473
case KILL_SUBMISSION:
472474
case STATUS:
473-
requiresAppResource = false;
475+
isSpecialCommand = true;
474476
parsedArgs.add(opt);
475477
parsedArgs.add(value);
476478
break;
477479
case HELP:
478480
case USAGE_ERROR:
479-
requiresAppResource = false;
480-
parsedArgs.add(opt);
481-
break;
482481
case VERSION:
483-
requiresAppResource = false;
482+
isSpecialCommand = true;
484483
parsedArgs.add(opt);
485484
break;
486485
default:

launcher/src/test/java/org/apache/spark/launcher/SparkSubmitCommandBuilderSuite.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.spark.launcher;
1919

2020
import java.io.File;
21+
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.HashMap;
@@ -27,14 +28,20 @@
2728

2829
import org.junit.AfterClass;
2930
import org.junit.BeforeClass;
31+
import org.junit.Rule;
3032
import org.junit.Test;
33+
import org.junit.rules.ExpectedException;
34+
3135
import static org.junit.Assert.*;
3236

3337
public class SparkSubmitCommandBuilderSuite extends BaseSuite {
3438

3539
private static File dummyPropsFile;
3640
private static SparkSubmitOptionParser parser;
3741

42+
@Rule
43+
public ExpectedException expectedException = ExpectedException.none();
44+
3845
@BeforeClass
3946
public static void setUp() throws Exception {
4047
dummyPropsFile = File.createTempFile("spark", "properties");
@@ -74,8 +81,11 @@ public void testCliHelpAndNoArg() throws Exception {
7481

7582
@Test
7683
public void testCliKillAndStatus() throws Exception {
77-
testCLIOpts(parser.STATUS);
78-
testCLIOpts(parser.KILL_SUBMISSION);
84+
List<String> params = Arrays.asList("driver-20160531171222-0000");
85+
testCLIOpts(null, parser.STATUS, params);
86+
testCLIOpts(null, parser.KILL_SUBMISSION, params);
87+
testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.STATUS, params);
88+
testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.KILL_SUBMISSION, params);
7989
}
8090

8191
@Test
@@ -190,6 +200,33 @@ public void testSparkRShell() throws Exception {
190200
env.get("SPARKR_SUBMIT_ARGS"));
191201
}
192202

203+
@Test(expected = IllegalArgumentException.class)
204+
public void testExamplesRunnerNoArg() throws Exception {
205+
List<String> sparkSubmitArgs = Arrays.asList(SparkSubmitCommandBuilder.RUN_EXAMPLE);
206+
Map<String, String> env = new HashMap<>();
207+
buildCommand(sparkSubmitArgs, env);
208+
}
209+
210+
@Test
211+
public void testExamplesRunnerNoMainClass() throws Exception {
212+
testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.HELP, null);
213+
testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.USAGE_ERROR, null);
214+
testCLIOpts(SparkSubmitCommandBuilder.RUN_EXAMPLE, parser.VERSION, null);
215+
}
216+
217+
@Test
218+
public void testExamplesRunnerWithMasterNoMainClass() throws Exception {
219+
expectedException.expect(IllegalArgumentException.class);
220+
expectedException.expectMessage("Missing example class name.");
221+
222+
List<String> sparkSubmitArgs = Arrays.asList(
223+
SparkSubmitCommandBuilder.RUN_EXAMPLE,
224+
parser.MASTER + "=foo"
225+
);
226+
Map<String, String> env = new HashMap<>();
227+
buildCommand(sparkSubmitArgs, env);
228+
}
229+
193230
@Test
194231
public void testExamplesRunner() throws Exception {
195232
List<String> sparkSubmitArgs = Arrays.asList(
@@ -344,10 +381,17 @@ private List<String> buildCommand(List<String> args, Map<String, String> env) th
344381
return newCommandBuilder(args).buildCommand(env);
345382
}
346383

347-
private void testCLIOpts(String opt) throws Exception {
348-
List<String> helpArgs = Arrays.asList(opt, "driver-20160531171222-0000");
384+
private void testCLIOpts(String appResource, String opt, List<String> params) throws Exception {
385+
List<String> args = new ArrayList<>();
386+
if (appResource != null) {
387+
args.add(appResource);
388+
}
389+
args.add(opt);
390+
if (params != null) {
391+
args.addAll(params);
392+
}
349393
Map<String, String> env = new HashMap<>();
350-
List<String> cmd = buildCommand(helpArgs, env);
394+
List<String> cmd = buildCommand(args, env);
351395
assertTrue(opt + " should be contained in the final cmd.",
352396
cmd.contains(opt));
353397
}

0 commit comments

Comments
 (0)