This is an automated email from the ASF dual-hosted git repository.
chengpan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git
The following commit(s) were added to refs/heads/master by this push:
new 93e9745 [KYUUBI #1293] Fix potential failure of engine discovery when
mixed use subdomain
93e9745 is described below
commit 93e9745a39a9d69db7cbd70ead63de0fff1e3c0b
Author: yanyu34946 <[email protected]>
AuthorDate: Thu Nov 18 18:00:30 2021 +0800
[KYUUBI #1293] Fix potential failure of engine discovery when mixed use
subdomain
### _Why are the changes needed?_
Fix #1293
This PR is a rework of #1303, also related to #962
We have two options to fix this issue. This PR implements option 2.
```
[option 1]
kyuubi.engine.share.level.subdomain: String -- default: "default"
kyuubi.engine.pool.size: Int -- default: -1
val subdomain =
if (kyuubi.engine.share.level.subdomain == "default" &&
kyuubi.engine.pool.size > 0) {
s"engine-pool-[num]"
} else {
kyuubi.engine.share.level.subdomain
}
[option 2]
kyuubi.engine.share.level.subdomain: Option[String] -- default: None
kyuubi.engine.pool.size: Int -- default: -1
val subdomain =
kyuubi.engine.share.level.subdomain match {
case Some(value) => value
case None if kyuubi.engine.pool.size > 0 => s"engine-pool-[num]"
case None => "default"
}
```
```
[diff]
[[case1]]
kyuubi.engine.share.level.subdomain=default
kyuubi.engine.pool.size=2
option 1 result: subdomain="engine-pool-[num]"
option 2 result: subdomain="default"
[[case2]]
kyuubi.engine.share.level.subdomain=hello
kyuubi.engine.pool.size=2
option 1 result: subdomain="hello"
option 2 result: subdomain="hello"
[[case3]]
kyuubi.engine.share.level.subdomain=
kyuubi.engine.pool.size=2
option 1 result: subdomain="engine-pool-[num]"
option 2 result: subdomain="engine-pool-[num]"
[[case4]]
kyuubi.engine.share.level.subdomain=
kyuubi.engine.pool.size=
option 1 result: subdomain="default"
option 2 result: subdomain="default"
[[case5]]
kyuubi.engine.share.level.subdomain="hello"
kyuubi.engine.pool.size=
option 1 result: subdomain="hello"
option 2 result: subdomain="hello"
```
### _How was this patch tested?_
- [x] Add some test cases that check the changes thoroughly including
negative and positive cases if possible
- [ ] Add screenshots for manual tests if appropriate
- [x] [Run
test](https://kyuubi.readthedocs.io/en/latest/develop_tools/testing.html#running-tests)
locally before make a pull request
Closes #1402 from pan3793/1293.
Closes #1293
f6c4a7db [Cheng Pan] nit
fd427bfb [yanyu34946] [KYUUBI #1293] Fix bootstrap potential failure when
mixed use subdomain
Lead-authored-by: yanyu34946 <[email protected]>
Co-authored-by: Cheng Pan <[email protected]>
Signed-off-by: Cheng Pan <[email protected]>
---
docs/deployment/settings.md | 2 +-
.../org/apache/kyuubi/config/KyuubiConf.scala | 6 ++--
.../scala/org/apache/kyuubi/engine/EngineRef.scala | 38 ++++++++------------
.../org/apache/kyuubi/engine/EngineRefSuite.scala | 42 +++++++++++++---------
.../operation/KyuubiOperationPerUserSuite.scala | 26 +++++++++++++-
5 files changed, 68 insertions(+), 46 deletions(-)
diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md
index fa658cd..02c315d 100644
--- a/docs/deployment/settings.md
+++ b/docs/deployment/settings.md
@@ -186,7 +186,7 @@ kyuubi\.engine\.pool<br>\.size\.threshold|<div
style='width: 65pt;word-wrap: bre
kyuubi\.engine\.session<br>\.initialize\.sql|<div style='width:
65pt;word-wrap: break-word;white-space: normal'></div>|<div style='width:
170pt;word-wrap: break-word;white-space: normal'>SemiColon-separated list of
SQL statements to be initialized in the newly created engine session before
queries. This configuration can not be used in JDBC url due to the limitation
of Beeline/JDBC driver.</div>|<div style='width: 30pt'>seq</div>|<div
style='width: 20pt'>1.3.0</div>
kyuubi\.engine\.share<br>\.level|<div style='width: 65pt;word-wrap:
break-word;white-space: normal'>USER</div>|<div style='width: 170pt;word-wrap:
break-word;white-space: normal'>Engines will be shared in different levels,
available configs are: <ul> <li>CONNECTION: engine will not be shared but only
used by the current client connection</li> <li>USER: engine will be shared by
all sessions created by a unique username, see also
kyuubi.engine.share.level.subdomain</li> <li>GROUP: engine w [...]
kyuubi\.engine\.share<br>\.level\.sub\.domain|<div style='width:
65pt;word-wrap: break-word;white-space: normal'><undefined></div>|<div
style='width: 170pt;word-wrap: break-word;white-space: normal'>(deprecated) -
Using kyuubi.engine.share.level.subdomain instead</div>|<div style='width:
30pt'>string</div>|<div style='width: 20pt'>1.2.0</div>
-kyuubi\.engine\.share<br>\.level\.subdomain|<div style='width: 65pt;word-wrap:
break-word;white-space: normal'><undefined></div>|<div style='width:
170pt;word-wrap: break-word;white-space: normal'>Allow end-users to create a
subdomain for the share level of an engine. A subdomain is a case-insensitive
string values that must be a valid zookeeper sub path. For example, for `USER`
share level, an end-user can share a certain engine within a subdomain, not for
all of its clients. End- [...]
+kyuubi\.engine\.share<br>\.level\.subdomain|<div style='width: 65pt;word-wrap:
break-word;white-space: normal'><undefined></div>|<div style='width:
170pt;word-wrap: break-word;white-space: normal'>Allow end-users to create a
subdomain for the share level of an engine. A subdomain is a case-insensitive
string values that must be a valid zookeeper sub path. For example, for `USER`
share level, an end-user can share a certain engine within a subdomain, not for
all of its clients. End- [...]
kyuubi\.engine\.single<br>\.spark\.session|<div style='width: 65pt;word-wrap:
break-word;white-space: normal'>false</div>|<div style='width: 170pt;word-wrap:
break-word;white-space: normal'>When set to true, this engine is running in a
single session mode. All the JDBC/ODBC connections share the temporary views,
function registries, SQL configuration and the current database.</div>|<div
style='width: 30pt'>boolean</div>|<div style='width: 20pt'>1.3.0</div>
kyuubi\.engine\.type|<div style='width: 65pt;word-wrap:
break-word;white-space: normal'>SPARK_SQL</div>|<div style='width:
170pt;word-wrap: break-word;white-space: normal'>Specify the detailed engine
that supported by the Kyuubi. The engine type bindings to SESSION scope. This
configuration is experimental. Currently, available configs are: <ul>
<li>SPARK_SQL: specify this engine type will launch a Spark engine which can
provide all the capacity of the Apache Spark. Note, it's a default [...]
kyuubi\.engine\.ui<br>\.retainedSessions|<div style='width: 65pt;word-wrap:
break-word;white-space: normal'>200</div>|<div style='width: 170pt;word-wrap:
break-word;white-space: normal'>The number of SQL client sessions kept in the
Kyuubi Query Engine web UI.</div>|<div style='width: 30pt'>int</div>|<div
style='width: 20pt'>1.4.0</div>
diff --git
a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
index a5c309c..2ad1368 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/config/KyuubiConf.scala
@@ -784,15 +784,13 @@ object KyuubiConf {
.checkValues(ShareLevel.values.map(_.toString))
.createWithDefault(ShareLevel.USER.toString)
- private val validEngineSubDomain: Pattern = "^[a-zA-Z_-]{1,14}$".r.pattern
-
// [ZooKeeper Data Model]
//
(http://zookeeper.apache.org/doc/r3.7.0/zookeeperProgrammers.html#ch_zkDataModel)
private val validEngineSubdomain: Pattern = ("(?!^[\\u002e]{1,2}$)" +
"(^[\\u0020-\\u002e\\u0030-\\u007e\\u00a0-\\ud7ff\\uf900-\\uffef]{1,}$)").r.pattern
@deprecated(s"using kyuubi.engine.share.level.subdomain instead", "1.4.0")
- val ENGINE_SHARE_LEVEL_SUB_DOMAIN: OptionalConfigEntry[String] =
+ val ENGINE_SHARE_LEVEL_SUB_DOMAIN: ConfigEntry[Option[String]] =
buildConf("engine.share.level.sub.domain")
.doc("(deprecated) - Using kyuubi.engine.share.level.subdomain instead")
.version("1.2.0")
@@ -809,7 +807,7 @@ object KyuubiConf {
" subdomain is a case-insensitive string values that must be a valid
zookeeper sub path." +
" For example, for `USER` share level, an end-user can share a certain
engine within" +
" a subdomain, not for all of its clients. End-users are free to
create multiple" +
- " engines in the `USER` share level")
+ " engines in the `USER` share level. When disable engine pool, use
'default' if absent.")
.version("1.4.0")
.fallbackConf(ENGINE_SHARE_LEVEL_SUB_DOMAIN)
diff --git
a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala
b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala
index 617b778..c33ffbd 100644
--- a/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala
+++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/engine/EngineRef.scala
@@ -68,25 +68,21 @@ private[kyuubi] class EngineRef(
// Server-side engine pool size threshold
private val poolThreshold: Int = conf.get(ENGINE_POOL_SIZE_THRESHOLD)
+ private val clientPoolSize: Int = conf.get(ENGINE_POOL_SIZE)
+
@VisibleForTesting
- private[kyuubi] val subdomain: Option[String] =
conf.get(ENGINE_SHARE_LEVEL_SUBDOMAIN).orElse {
- val clientPoolSize: Int = conf.get(ENGINE_POOL_SIZE)
-
- if (clientPoolSize > 0) {
- val poolSize = if (clientPoolSize <= poolThreshold) {
- clientPoolSize
- } else {
- warn(s"Request engine pool size($clientPoolSize) exceeds, fallback to
system threshold " +
- s"$poolThreshold")
- poolThreshold
+ private[kyuubi] val subdomain: String =
conf.get(ENGINE_SHARE_LEVEL_SUBDOMAIN) match {
+ case Some(_subdomain) => _subdomain
+ case None if clientPoolSize > 0 =>
+ val poolSize = math.min(clientPoolSize, poolThreshold)
+ if (poolSize < clientPoolSize) {
+ warn(s"Request engine pool size($clientPoolSize) exceeds, fallback to
" +
+ s"system threshold $poolThreshold")
}
-
// TODO: Currently, we use random policy, and later we can add a
sequential policy,
// such as AtomicInteger % poolSize.
- Some("engine-pool-" + Random.nextInt(poolSize))
- } else {
- None
- }
+ "engine-pool-" + Random.nextInt(poolSize)
+ case _ => "default" // [KYUUBI #1293]
}
// Launcher of the engine
@@ -113,10 +109,7 @@ private[kyuubi] class EngineRef(
val commonNamePrefix = s"kyuubi_${shareLevel}_${engineType}_${appUser}"
shareLevel match {
case CONNECTION => s"${commonNamePrefix}_$engineRefId"
- case _ => subdomain match {
- case Some(domain) => s"${commonNamePrefix}_${domain}_$engineRefId"
- case _ => s"${commonNamePrefix}_$engineRefId"
- }
+ case _ => s"${commonNamePrefix}_${subdomain}_$engineRefId"
}
}
@@ -137,10 +130,7 @@ private[kyuubi] class EngineRef(
val commonParent = s"${serverSpace}_${shareLevel}_$engineType"
shareLevel match {
case CONNECTION => ZKPaths.makePath(commonParent, appUser, engineRefId)
- case _ => subdomain match {
- case Some(domain) => ZKPaths.makePath(commonParent, appUser, domain)
- case None => ZKPaths.makePath(commonParent, appUser)
- }
+ case _ => ZKPaths.makePath(commonParent, appUser, subdomain)
}
}
@@ -152,7 +142,7 @@ private[kyuubi] class EngineRef(
case CONNECTION => f
case _ =>
val lockPath =
- ZKPaths.makePath(s"${serverSpace}_$shareLevel", "lock", appUser,
subdomain.orNull)
+ ZKPaths.makePath(s"${serverSpace}_$shareLevel", "lock", appUser,
subdomain)
var lock: InterProcessSemaphoreMutex = null
try {
try {
diff --git
a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefSuite.scala
b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefSuite.scala
index dc3405e..808114d 100644
--- a/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefSuite.scala
+++ b/kyuubi-server/src/test/scala/org/apache/kyuubi/engine/EngineRefSuite.scala
@@ -77,8 +77,8 @@ class EngineRefSuite extends KyuubiFunSuite {
conf.set(KyuubiConf.ENGINE_SHARE_LEVEL, USER.toString)
conf.set(KyuubiConf.ENGINE_TYPE, FLINK_SQL.toString)
val appName = new EngineRef(conf, user, id)
- assert(appName.engineSpace ===
ZKPaths.makePath(s"kyuubi_${USER}_$FLINK_SQL", user))
- assert(appName.defaultEngineName ===
s"kyuubi_${USER}_${FLINK_SQL}_${user}_$id")
+ assert(appName.engineSpace ===
ZKPaths.makePath(s"kyuubi_${USER}_$FLINK_SQL", user, "default"))
+ assert(appName.defaultEngineName ===
s"kyuubi_${USER}_${FLINK_SQL}_${user}_default_$id")
Seq(KyuubiConf.ENGINE_SHARE_LEVEL_SUBDOMAIN,
KyuubiConf.ENGINE_SHARE_LEVEL_SUB_DOMAIN).foreach { k =>
@@ -97,8 +97,10 @@ class EngineRefSuite extends KyuubiFunSuite {
conf.set(KyuubiConf.ENGINE_TYPE, SPARK_SQL.toString)
val engineRef = new EngineRef(conf, user, id)
val primaryGroupName =
UserGroupInformation.createRemoteUser(user).getPrimaryGroupName
- assert(engineRef.engineSpace ===
ZKPaths.makePath(s"kyuubi_GROUP_SPARK_SQL", primaryGroupName))
- assert(engineRef.defaultEngineName ===
s"kyuubi_GROUP_SPARK_SQL_${primaryGroupName}_$id")
+ assert(engineRef.engineSpace ===
+ ZKPaths.makePath(s"kyuubi_GROUP_SPARK_SQL", primaryGroupName, "default"))
+ assert(engineRef.defaultEngineName ===
+ s"kyuubi_GROUP_SPARK_SQL_${primaryGroupName}_default_$id")
Seq(KyuubiConf.ENGINE_SHARE_LEVEL_SUBDOMAIN,
KyuubiConf.ENGINE_SHARE_LEVEL_SUB_DOMAIN).foreach { k =>
@@ -125,8 +127,8 @@ class EngineRefSuite extends KyuubiFunSuite {
conf.set(KyuubiConf.ENGINE_TYPE, FLINK_SQL.toString)
val appName = new EngineRef(conf, user, id)
assert(appName.engineSpace ===
- ZKPaths.makePath(s"kyuubi_${SERVER}_${FLINK_SQL}", user))
- assert(appName.defaultEngineName ===
s"kyuubi_${SERVER}_${FLINK_SQL}_${user}_$id")
+ ZKPaths.makePath(s"kyuubi_${SERVER}_${FLINK_SQL}", user, "default"))
+ assert(appName.defaultEngineName ===
s"kyuubi_${SERVER}_${FLINK_SQL}_${user}_default_$id")
conf.set(KyuubiConf.ENGINE_SHARE_LEVEL_SUBDOMAIN.key, "abc")
val appName2 = new EngineRef(conf, user, id)
@@ -138,27 +140,35 @@ class EngineRefSuite extends KyuubiFunSuite {
test("check the engine space of engine pool") {
val id = UUID.randomUUID().toString
- // test subdomain
+ // set subdomain and disable engine pool
conf.set(ENGINE_SHARE_LEVEL_SUBDOMAIN.key, "abc")
+ conf.set(ENGINE_POOL_SIZE, -1)
val engine1 = new EngineRef(conf, user, id)
- assert(engine1.subdomain === Some("abc"))
+ assert(engine1.subdomain === "abc")
- // unset domain
+ // unset subdomain and disable engine pool
conf.unset(ENGINE_SHARE_LEVEL_SUBDOMAIN)
+ conf.set(ENGINE_POOL_SIZE, -1)
val engine2 = new EngineRef(conf, user, id)
- assert(engine2.subdomain === None)
+ assert(engine2.subdomain === "default")
- // 1 <= engine pool size < threshold
+ // set subdomain and 1 <= engine pool size < threshold
+ conf.set(ENGINE_SHARE_LEVEL_SUBDOMAIN.key, "abc")
+ conf.set(ENGINE_POOL_SIZE, 1)
+ val engine3 = new EngineRef(conf, user, id)
+ assert(engine3.subdomain === "abc")
+
+ // unset subdomain and 1 <= engine pool size < threshold
conf.unset(ENGINE_SHARE_LEVEL_SUBDOMAIN)
conf.set(ENGINE_POOL_SIZE, 3)
- val engine3 = new EngineRef(conf, user, id)
- assert(engine3.subdomain.get.startsWith("engine-pool-"))
+ val engine4 = new EngineRef(conf, user, id)
+ assert(engine4.subdomain.startsWith("engine-pool-"))
- // engine pool size > threshold
+ // unset subdomain and engine pool size > threshold
conf.unset(ENGINE_SHARE_LEVEL_SUBDOMAIN)
conf.set(ENGINE_POOL_SIZE, 100)
- val engine4 = new EngineRef(conf, user, id)
- val engineNumber = Integer.parseInt(engine4.subdomain.get.substring(12))
+ val engine5 = new EngineRef(conf, user, id)
+ val engineNumber = Integer.parseInt(engine5.subdomain.substring(12))
val threshold = ENGINE_POOL_SIZE_THRESHOLD.defaultVal.get
assert(engineNumber <= threshold)
}
diff --git
a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala
b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala
index ee31b27..a4358b5 100644
---
a/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala
+++
b/kyuubi-server/src/test/scala/org/apache/kyuubi/operation/KyuubiOperationPerUserSuite.scala
@@ -83,7 +83,7 @@ class KyuubiOperationPerUserSuite extends WithKyuubiServer
with SparkQueryTests
}
}
- test("ensure two connections share the same engine when specifying
subDomain.") {
+ test("ensure two connections share the same engine when specifying
subdomain.") {
withSessionConf()(
Map(
KyuubiConf.ENGINE_SHARE_LEVEL_SUBDOMAIN.key -> "abc"
@@ -114,4 +114,28 @@ class KyuubiOperationPerUserSuite extends WithKyuubiServer
with SparkQueryTests
assert(r1 === r2)
}
}
+
+ test("ensure engine discovery works when mixed use subdomain") {
+ var r1: String = null
+ var r2: String = null
+ withSessionConf()(Map.empty)(Map.empty) {
+ withJdbcStatement() { statement =>
+ val res = statement.executeQuery("set spark.app.name")
+ assert(res.next())
+ r1 = res.getString("value")
+ }
+ }
+ assert(r1 contains "default")
+
+ withSessionConf()(Map(KyuubiConf.ENGINE_SHARE_LEVEL_SUBDOMAIN.key ->
"abc"))(Map.empty) {
+ withJdbcStatement() { statement =>
+ val res = statement.executeQuery("set spark.app.name")
+ assert(res.next())
+ r2 = res.getString("value")
+ }
+ }
+ assert(r2 contains "abc")
+
+ assert(r1 !== r2)
+ }
}