Skip to content

Commit d5a62ab

Browse files
maomaodevpan3793
authored andcommitted
[KYUUBI #7443] Fix NullPointerException when configuring the wrong SPARK_HOME
### Why are the changes needed? Fix NullPointerException when configuring the wrong SPARK_HOME. For example: ``` bin/kyuubi-beeline \ -u 'jdbc:kyuubi://localhost:10009/default' \ --conf kyuubi.engineEnv.SPARK_HOME=/path/of/not/exist ``` ### How was this patch tested? 1. Unit test 2. Manual test ### Was this patch authored or co-authored using generative AI tooling? Partially assisted by Claude Code (Claude Opus 4.7) for unit test. Closes #7450 from maomaodev/kyuubi-7443. Closes #7443 cb2e361 [lifumao] Fix NullPointerException when configuring the wrong SPARK_HOME Authored-by: lifumao <lifumao@tencent.com> Signed-off-by: Cheng Pan <chengpan@apache.org>
1 parent 1699e17 commit d5a62ab

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

kyuubi-server/src/main/scala/org/apache/kyuubi/engine/spark/SparkProcessBuilder.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,20 @@ class SparkProcessBuilder(
111111
}
112112

113113
private[kyuubi] def extractSparkCoreScalaVersion(fileNames: Iterable[String]): String = {
114-
fileNames.collectFirst { case SPARK_CORE_SCALA_VERSION_REGEX(scalaVersion) => scalaVersion }
114+
Option(fileNames).getOrElse(Iterable.empty)
115+
.collectFirst { case SPARK_CORE_SCALA_VERSION_REGEX(scalaVersion) => scalaVersion }
115116
.getOrElse(throw new KyuubiException("Failed to extract Scala version from spark-core jar"))
116117
}
117118

118119
override protected val engineScalaBinaryVersion: String = {
119120
env.get("SPARK_SCALA_VERSION").filter(StringUtils.isNotBlank).getOrElse {
120-
extractSparkCoreScalaVersion(Paths.get(sparkHome, "jars").toFile.list())
121+
val jarsDir = Paths.get(sparkHome, "jars").toFile
122+
val fileNames = Option(jarsDir.list()).getOrElse {
123+
throw new KyuubiException(
124+
s"Failed to list jars under $sparkHome, please check if SPARK_HOME is configured " +
125+
"correctly and the jars directory exists")
126+
}
127+
extractSparkCoreScalaVersion(fileNames)
121128
}
122129
}
123130

kyuubi-server/src/test/scala/org/apache/kyuubi/engine/spark/SparkProcessBuilderSuite.scala

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,70 @@ class SparkProcessBuilderSuite extends KerberizedTestHelper with MockitoSugar {
422422
}
423423
}
424424

425+
test("Fix NullPointerException when SPARK_HOME is invalid") {
426+
val notFoundMsg = "Failed to extract Scala version"
427+
val listFailMsg = "Failed to list jars"
428+
429+
def newConf(sparkHome: String): KyuubiConf = {
430+
KyuubiConf(false)
431+
.set("kyuubi.engineEnv.SPARK_HOME", sparkHome)
432+
.set("kyuubi.engineEnv.SPARK_SCALA_VERSION", "")
433+
}
434+
435+
// case 1: SPARK_HOME does not exists
436+
val nonExistentHome = Paths.get(
437+
Utils.createTempDir("invalid-spark-home").toString,
438+
s"not-exist-${UUID.randomUUID()}").toString
439+
val nonExistentErr = intercept[KyuubiException] {
440+
new SparkProcessBuilder("kentyao", true, newConf(nonExistentHome))
441+
}
442+
assert(!nonExistentErr.getMessage.contains("NullPointerException"))
443+
assert(nonExistentErr.getMessage.contains(listFailMsg))
444+
445+
// case 2: SPARK_HOME exists but the `jars` subdirectory is missing
446+
val homeWithoutJarsDir = Utils.createTempDir("spark-home-no-jars-dir")
447+
val withoutJarsErr = intercept[KyuubiException] {
448+
new SparkProcessBuilder("kentyao", true, newConf(homeWithoutJarsDir.toString))
449+
}
450+
assert(!withoutJarsErr.getMessage.contains("NullPointerException"))
451+
assert(withoutJarsErr.getMessage.contains(listFailMsg))
452+
453+
// case 3: SPARK_HOME exists, but the `jars` is a regular file instead of a directory
454+
val homeWithJarsAsFile = Utils.createTempDir("spark-home-jars-as-file")
455+
Files.createFile(homeWithJarsAsFile.resolve("jars"))
456+
val jarsAsFileErr = intercept[KyuubiException] {
457+
new SparkProcessBuilder("kentyao", true, newConf(homeWithJarsAsFile.toString))
458+
}
459+
assert(!jarsAsFileErr.getMessage.contains("NullPointerException"))
460+
assert(jarsAsFileErr.getMessage.contains(listFailMsg))
461+
462+
// case 4: SPARK_HOME exists, but the `jars` is an empty directory
463+
val homeWithEmptyJars = Utils.createTempDir("spark-home-empty-jars")
464+
Files.createDirectory(homeWithEmptyJars.resolve("jars"))
465+
val emptyJarsHomeErr = intercept[KyuubiException] {
466+
new SparkProcessBuilder("kentyao", true, newConf(homeWithEmptyJars.toString))
467+
}
468+
assert(!emptyJarsHomeErr.getMessage.contains("NullPointerException"))
469+
assert(emptyJarsHomeErr.getMessage.contains(notFoundMsg))
470+
471+
// case 5: SPARK_HOME exists, but the `jars` does not contain any spark-core jar
472+
val homeWithoutCoreJar = Utils.createTempDir("spark-home-no-core")
473+
val noCoreJarsDir = Files.createDirectory(homeWithoutCoreJar.resolve("jars"))
474+
Files.createFile(noCoreJarsDir.resolve("hadoop-common-3.3.4.jar"))
475+
Files.createFile(noCoreJarsDir.resolve("scala-library-2.12.18.jar"))
476+
val noCoreErr = intercept[KyuubiException] {
477+
new SparkProcessBuilder("kentyao", true, newConf(homeWithoutCoreJar.toString))
478+
}
479+
assert(!noCoreErr.getMessage.contains("NullPointerException"))
480+
assert(noCoreErr.getMessage.contains(notFoundMsg))
481+
482+
// case 6: SPARK_HOME exists, and the `jars` contains spark-core jar
483+
val validSparkHome = Utils.createTempDir("spark-home-valid")
484+
val validJarsDir = Files.createDirectory(validSparkHome.resolve("jars"))
485+
Files.createFile(validJarsDir.resolve("spark-core_2.12-3.5.0.jar"))
486+
new SparkProcessBuilder("kentyao", true, newConf(validSparkHome.toString))
487+
}
488+
425489
test("match scala version of spark home") {
426490
Seq(
427491
"spark-3.2.4-bin-hadoop3.2",

0 commit comments

Comments
 (0)